Bazel का पैरलल इवैलुएशन और इंक्रीमेंटलिटी मॉडल.
डेटा मॉडल
डेटा मॉडल में ये आइटम शामिल होते हैं:
SkyValue
. इन्हें नोड भी कहा जाता है.SkyValues
ऐसे ऑब्जेक्ट होते हैं जिनमें बदलाव नहीं किया जा सकता. इनमें, बिल्ड के दौरान बनाया गया सारा डेटा और बिल्ड के इनपुट शामिल होते हैं. उदाहरण के लिए: इनपुट फ़ाइलें, आउटपुट फ़ाइलें, टारगेट, और कॉन्फ़िगर किए गए टारगेट.SkyKey
.SkyValue
का रेफ़रंस देने के लिए, छोटा और अपरिवर्तनीय नाम. उदाहरण के लिए,FILECONTENTS:/tmp/foo
याPACKAGE://foo
.SkyFunction
. अपनी कुंजियों और उन पर निर्भर नोड के आधार पर नोड बनाता है.- नोड ग्राफ़. ऐसा डेटा स्ट्रक्चर जिसमें नोड के बीच डिपेंडेंसी का संबंध होता है.
Skyframe
. इंक्रीमेंटल इवैलुएशन फ़्रेमवर्क का कोड नेम, जिस पर Bazel आधारित है.
आकलन
बिल्ड का अनुरोध करने वाले नोड का आकलन करके, बिल्ड किया जाता है.
सबसे पहले, Bazel टॉप-लेवल की SkyKey
कुंजी से जुड़ा SkyFunction
ढूंढता है. इसके बाद, फ़ंक्शन उन नोड का आकलन करने का अनुरोध करता है जिनकी ज़रूरत टॉप-लेवल नोड का आकलन करने के लिए होती है. इससे, लीफ़ नोड तक पहुंचने तक, अन्य SkyFunction
कॉल होते हैं. आम तौर पर, लीफ़ नोड, फ़ाइल सिस्टम में इनपुट फ़ाइलों को दिखाते हैं. आखिर में, Bazel को टॉप-लेवल SkyValue
की वैल्यू, कुछ साइड इफ़ेक्ट (जैसे, फ़ाइल सिस्टम में आउटपुट फ़ाइलें) मिलती हैं. साथ ही, उसे बिल्ड में शामिल नोड के बीच की डिपेंडेंसी का डायरेक्टेड ऐसाइक्लिक ग्राफ़ भी मिलता है.
अगर SkyFunction
, अपने काम के लिए ज़रूरी सभी नोड के बारे में पहले से नहीं बता सकता, तो वह एक से ज़्यादा पास में SkyKeys
का अनुरोध कर सकता है. एक आसान उदाहरण, किसी इनपुट फ़ाइल नोड का आकलन करना है, जो एक सिर्फ़ लिंक है: फ़ंक्शन, फ़ाइल को पढ़ने की कोशिश करता है और उसे पता चलता है कि यह एक सिर्फ़ लिंक है. इसलिए, वह सिर्फ़ लिंक के टारगेट को दिखाने वाला फ़ाइल सिस्टम नोड फ़ेच करता है. हालांकि, यह खुद भी एक सिमलिंक हो सकता है. ऐसे में, ओरिजनल फ़ंक्शन को अपने टारगेट को भी फ़ेच करना होगा.
कोड में फ़ंक्शन को इंटरफ़ेस SkyFunction
से दिखाया जाता है. साथ ही, SkyFunction.Environment
नाम के इंटरफ़ेस से उसे दी जाने वाली सेवाओं को दिखाया जाता है. फ़ंक्शन ये काम कर सकते हैं:
env.getValue
को कॉल करके, किसी दूसरे नोड के आकलन का अनुरोध करें. अगर नोड उपलब्ध है, तो उसकी वैल्यू दिखाई जाती है. ऐसा न होने पर,null
दिखाया जाता है. साथ ही, फ़ंक्शन केnull
दिखाने की उम्मीद की जाती है. बाद वाले मामले में, डिपेंडेंट नोड का आकलन किया जाता है. इसके बाद, ओरिजनल नोड बिल्डर को फिर से शुरू किया जाता है. हालांकि, इस बार वहीenv.getValue
कॉल,null
वैल्यू के बजाय कोई दूसरी वैल्यू दिखाएगा.env.getValues()
को कॉल करके, कई अन्य नोड के आकलन का अनुरोध करें. यह भी वही काम करता है. हालांकि, इसमें डिपेंडेंट नोड का आकलन एक साथ किया जाता है.- इनवोकेशन के दौरान कैलकुलेशन करना
- इसके साइड इफ़ेक्ट हों. उदाहरण के लिए, फ़ाइल सिस्टम में फ़ाइलें लिखना. ध्यान रखें कि दो अलग-अलग फ़ंक्शन एक-दूसरे के काम में रुकावट न डालें. आम तौर पर, लिखने से जुड़े साइड इफ़ेक्ट (जहां डेटा, Bazel से बाहर की ओर फ़्लो करता है) ठीक होते हैं. वहीं, पढ़ने से जुड़े साइड इफ़ेक्ट (जहां डेटा, रजिस्टर की गई डिपेंडेंसी के बिना Bazel में अंदर की ओर फ़्लो करता है) ठीक नहीं होते. ऐसा इसलिए, क्योंकि ये रजिस्टर की गई डिपेंडेंसी नहीं हैं. इसलिए, इनकी वजह से इंक्रीमेंटल बिल्ड गलत हो सकते हैं.
सही तरीके से काम करने वाले SkyFunction
लागू करने के लिए, डेटा को ऐक्सेस करने का कोई दूसरा तरीका इस्तेमाल नहीं किया जाता. जैसे, सीधे फ़ाइल सिस्टम को पढ़कर. ऐसा इसलिए, क्योंकि इससे Bazel, पढ़ी गई फ़ाइल पर डेटा डिपेंडेंसी रजिस्टर नहीं करता. इस वजह से, इंक्रीमेंटल बिल्ड गलत होते हैं.
जब किसी फ़ंक्शन के पास अपना काम करने के लिए ज़रूरत के मुताबिक डेटा हो जाता है, तो उसे null
के बजाय कोई ऐसी वैल्यू दिखानी चाहिए जो काम पूरा होने का संकेत दे.
आकलन की इस रणनीति के कई फ़ायदे हैं:
- हर्मेटिकिटी. अगर फ़ंक्शन सिर्फ़ दूसरे नोड पर निर्भर करके इनपुट डेटा का अनुरोध करते हैं, तो Bazel यह गारंटी दे सकता है कि इनपुट स्टेटस एक जैसा होने पर, एक जैसा डेटा दिखेगा. अगर सभी Sky फ़ंक्शन डिटरमिनिस्टिक हैं, तो इसका मतलब है कि पूरा बिल्ड भी डिटरमिनिस्टिक होगा.
- सही और बेहतर इंक्रीमेंटलिटी. अगर सभी फ़ंक्शन का सारा इनपुट डेटा रिकॉर्ड किया जाता है, तो Bazel सिर्फ़ उन नोड के सटीक सेट को अमान्य कर सकता है जिन्हें इनपुट डेटा में बदलाव होने पर अमान्य करना ज़रूरी है.
- पैरलल प्रोसेसिंग. फ़ंक्शन, डिपेंडेंसी का अनुरोध करके ही एक-दूसरे के साथ इंटरैक्ट कर सकते हैं. इसलिए, एक-दूसरे पर निर्भर न करने वाले फ़ंक्शन को एक साथ चलाया जा सकता है. साथ ही, Bazel यह गारंटी दे सकता है कि नतीजा वैसा ही होगा जैसा कि उन्हें क्रम से चलाने पर होता है.
बढ़ोतरी
फ़ंक्शन, सिर्फ़ दूसरे नोड पर निर्भर करके इनपुट डेटा को ऐक्सेस कर सकते हैं. इसलिए, Bazel, इनपुट फ़ाइलों से आउटपुट फ़ाइलों तक का पूरा डेटा फ़्लो ग्राफ़ बना सकता है. साथ ही, इस जानकारी का इस्तेमाल सिर्फ़ उन नोड को फिर से बनाने के लिए कर सकता है जिन्हें असल में फिर से बनाने की ज़रूरत है: बदली गई इनपुट फ़ाइलों के सेट का रिवर्स ट्रांसिटिव क्लोज़र.
खास तौर पर, इंक्रीमेंटलिटी की दो संभावित रणनीतियां मौजूद हैं: सबसे नीचे से शुरू होने वाली रणनीति और सबसे ऊपर से शुरू होने वाली रणनीति. कौनसा विकल्प सबसे सही है, यह इस बात पर निर्भर करता है कि डिपेंडेंसी ग्राफ़ कैसा दिखता है.
बॉटम-अप इनवैलिडेशन के दौरान, ग्राफ़ बनने और बदले गए इनपुट के सेट के बारे में पता चलने के बाद, उन सभी नोड को अमान्य कर दिया जाता है जो बदली गई फ़ाइलों पर ट्रांज़िटिव तरीके से निर्भर होते हैं. यह तब सबसे सही होता है, जब एक ही टॉप-लेवल नोड को फिर से बनाया जाएगा. ध्यान दें कि नीचे से ऊपर की ओर अमान्य करने के लिए, पिछले बिल्ड की सभी इनपुट फ़ाइलों पर
stat()
चलाना ज़रूरी है. इससे यह पता चलता है कि उनमें बदलाव हुआ है या नहीं. बदली गई फ़ाइलों के बारे में जानने के लिए,inotify
या मिलते-जुलते तरीके का इस्तेमाल करके, इस प्रोसेस को बेहतर बनाया जा सकता है.टॉप-डाउन इनवैलिडेशन के दौरान, टॉप-लेवल नोड के ट्रांसीटिव क्लोज़र की जांच की जाती है. साथ ही, सिर्फ़ उन नोड को रखा जाता है जिनका ट्रांसीटिव क्लोज़र क्लीन है. अगर नोड ग्राफ़ बड़ा है, लेकिन अगले बिल्ड के लिए सिर्फ़ उसका छोटा सबसेट चाहिए, तो यह तरीका बेहतर है: बॉटम-अप इनवैलिडेशन से पहले बिल्ड का बड़ा ग्राफ़ अमान्य हो जाएगा. वहीं, टॉप-डाउन इनवैलिडेशन सिर्फ़ दूसरे बिल्ड के छोटे ग्राफ़ को अमान्य करता है.
Bazel सिर्फ़ नीचे से ऊपर की ओर अमान्य करता है.
ज़्यादा इंक्रीमेंट पाने के लिए, Bazel बदलाव को कम करने का इस्तेमाल करता है: अगर किसी नोड को अमान्य कर दिया गया है, लेकिन फिर से बनाने पर पता चलता है कि उसकी नई वैल्यू, उसकी पुरानी वैल्यू जैसी ही है, तो इस नोड में हुए बदलाव की वजह से अमान्य किए गए नोड "फिर से चालू" हो जाते हैं.
उदाहरण के लिए, अगर कोई व्यक्ति C++ फ़ाइल में किसी टिप्पणी में बदलाव करता है, तो इससे जनरेट की गई .o
फ़ाइल पहले जैसी ही रहेगी. इसलिए, लिंकर को फिर से कॉल करने की ज़रूरत नहीं है.
इंक्रीमेंटल लिंकिंग / कंपाइलेशन
इस मॉडल की मुख्य सीमा यह है कि किसी नोड को अमान्य करना, पूरी तरह से या कुछ भी नहीं करने जैसा है: जब कोई डिपेंडेंसी बदलती है, तो डिपेंडेंट नोड को हमेशा फिर से शुरू से बनाया जाता है. भले ही, कोई बेहतर एल्गोरिदम मौजूद हो, जो बदलावों के आधार पर नोड की पुरानी वैल्यू में बदलाव कर देगा. यहां कुछ उदाहरण दिए गए हैं जिनमें यह सुविधा काम की हो सकती है:
- इंक्रीमेंटल लिंकिंग
- जब किसी JAR फ़ाइल में एक क्लास फ़ाइल में बदलाव होता है, तो उसे फिर से बनाने के बजाय, JAR फ़ाइल में बदलाव किया जा सकता है.
Bazel, इन चीज़ों के साथ काम नहीं करता, क्योंकि:
- परफ़ॉर्मेंस में ज़्यादा फ़ायदा नहीं हुआ.
- यह पुष्टि करना मुश्किल है कि म्यूटेशन का नतीजा, क्लीन रीबिल्ड के नतीजे जैसा ही है. साथ ही, Google ऐसे बिल्ड को प्राथमिकता देता है जिन्हें बिट-टू-बिट दोहराया जा सकता है.
अब तक, ज़्यादा समय लेने वाले बिल्ड चरण को अलग-अलग हिस्सों में बांटकर और उसी तरह से फिर से आंकलन करके, अच्छी परफ़ॉर्मेंस हासिल की जा सकती थी. उदाहरण के लिए, किसी Android ऐप्लिकेशन में, सभी क्लास को कई ग्रुप में बांटा जा सकता है और उन्हें अलग-अलग डीईएक्स किया जा सकता है. इस तरह, अगर किसी ग्रुप में मौजूद क्लास में कोई बदलाव नहीं होता है, तो डेक्सिंग की प्रोसेस को फिर से शुरू करने की ज़रूरत नहीं होती.
Bazel के कॉन्सेप्ट पर आधारित मैपिंग
यहां मुख्य SkyFunction
और SkyValue
के बारे में खास जानकारी दी गई है. Bazel, इनका इस्तेमाल करके बिल्ड करता है:
- FileStateValue.
lstat()
का नतीजा. मौजूदा फ़ाइलों के लिए, फ़ंक्शन में ज़्यादा जानकारी भी कैलकुलेट की जाती है, ताकि फ़ाइल में हुए बदलावों का पता लगाया जा सके. यह Skyframe ग्राफ़ में सबसे निचले लेवल का नोड है और इस पर किसी भी चीज़ की डिपेंडेंसी नहीं होती. - FileValue. इसका इस्तेमाल, किसी फ़ाइल के असल कॉन्टेंट या रिज़ॉल्व किए गए पाथ के बारे में जानकारी देने वाली किसी भी चीज़ के लिए किया जाता है. यह संबंधित
FileStateValue
और उन सभी सिमलंक पर निर्भर करता है जिन्हें हल करना ज़रूरी है. जैसे,a/b
के लिएFileValue
कोa
औरa/b
के हल किए गए पाथ की ज़रूरत होती है.FileValue
औरFileStateValue
के बीच का अंतर अहम है, क्योंकिFileStateValue
का इस्तेमाल उन मामलों में किया जा सकता है जहां फ़ाइल के कॉन्टेंट की ज़रूरत नहीं होती. उदाहरण के लिए, फ़ाइल सिस्टम ग्लोब (जैसे,srcs=glob(["*/*.java"])
) का आकलन करते समय, फ़ाइल का कॉन्टेंट काम का नहीं होता. - DirectoryListingStateValue.
readdir()
का नतीजा.FileStateValue
की तरह ही, यह सबसे निचले लेवल का नोड है और इस पर कोई डिपेंडेंसी नहीं है. - DirectoryListingValue. इसका इस्तेमाल, डायरेक्ट्री की एंट्री से जुड़ी किसी भी चीज़ के लिए किया जाता है. यह डायरेक्ट्री के
DirectoryListingStateValue
और उससे जुड़ेFileValue
पर निर्भर करता है. - PackageValue. यह किसी BUILD फ़ाइल के पार्स किए गए वर्शन को दिखाता है. यह
BUILD
फ़ाइल केFileValue
पर निर्भर करता है. साथ ही, यह पैकेज में मौजूद ग्लोब को हल करने के लिए इस्तेमाल किए गए किसी भीDirectoryListingValue
पर भी निर्भर करता है. ग्लोब,BUILD
फ़ाइल के कॉन्टेंट को अंदरूनी तौर पर दिखाने वाला डेटा स्ट्रक्चर होता है. - ConfiguredTargetValue. कॉन्फ़िगर किए गए टारगेट को दिखाता है. यह टारगेट के विश्लेषण के दौरान जनरेट की गई कार्रवाइयों के सेट का टुपल होता है. साथ ही, यह कॉन्फ़िगर किए गए उन टारगेट के लिए दी गई जानकारी भी होती है जो इस पर निर्भर होते हैं. यह
PackageValue
पर निर्भर करता है, जो संबंधित टारगेट में है, डायरेक्ट डिपेंडेंसी केConfiguredTargetValues
, और बिल्ड कॉन्फ़िगरेशन को दिखाने वाले खास नोड पर. - ArtifactValue. यह बिल्ड में मौजूद किसी फ़ाइल को दिखाता है. भले ही, वह सोर्स हो या आउटपुट आर्टफ़ैक्ट. आर्टफ़ैक्ट, फ़ाइलों के बराबर होते हैं. इनका इस्तेमाल, बिल्ड के चरणों को लागू करने के दौरान फ़ाइलों को रेफ़र करने के लिए किया जाता है. सोर्स फ़ाइलें, उससे जुड़े नोड के
FileValue
पर निर्भर करती हैं. साथ ही, आर्टफ़ैक्ट जनरेट करने वाली हर कार्रवाई केActionExecutionValue
पर आउटपुट आर्टफ़ैक्ट निर्भर करते हैं. - ActionExecutionValue. किसी कार्रवाई के होने की जानकारी देता है. यह इनपुट फ़ाइलों के
ArtifactValues
पर निर्भर करता है. यह कार्रवाई, SkyKey में होती है. यह इस कॉन्सेप्ट के उलट है कि SkyKey छोटे होने चाहिए. ध्यान दें कि अगर एक्सीक्यूशन फ़ेज़ नहीं चलता है, तोActionExecutionValue
औरArtifactValue
का इस्तेमाल नहीं किया जाता.
विज़ुअल के तौर पर, यह डायग्राम Bazel के बिल्ड के बाद, SkyFunction के लागू होने के बीच के संबंधों को दिखाता है: