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 से डेटा बाहर की ओर बहता है) लिखें,
सही तरीके से लागू किए गए SkyFunction
इंप्लीमेंटेशन, डिपेंडेंसी का अनुरोध करने के अलावा किसी और तरीके से डेटा ऐक्सेस करने से बचते हैं, जैसे कि फ़ाइल सिस्टम को सीधे पढ़ना. ऐसा इसलिए, क्योंकि इसकी वजह से Bazel, पढ़ी गई फ़ाइल पर डेटा डिपेंडेंसी को रजिस्टर नहीं करता है. इस वजह से, इवेंट का डेटा गलत बढ़ता है.
जब किसी फ़ंक्शन में काम करने के लिए काफ़ी डेटा हो, तो उसे पूरा होने का संकेत देने वाली गैर-null
वैल्यू दिखानी चाहिए.
आकलन करने की इस रणनीति के कई फ़ायदे हैं:
- हर्मेटिटी. अगर फ़ंक्शन सिर्फ़ दूसरे नोड के आधार पर इनपुट डेटा का अनुरोध करते हैं, तो Bazel इस बात की गारंटी दे सकता है कि अगर इनपुट की स्थिति एक जैसी हो, तो वही डेटा दिखाया जाएगा. अगर सभी स्काई फ़ंक्शन डिटर्मिनिस्टिक हैं, तो इसका मतलब है कि पूरा बिल्ड भी डिटर्मिनिस्टिक होगा.
- सही और सटीक बढ़ोतरी. अगर सभी फ़ंक्शन का पूरा इनपुट डेटा रिकॉर्ड किया जाता है, तो Bazel नोड के सिर्फ़ उन सटीक सेट को अमान्य कर सकता है जिन्हें इनपुट डेटा में बदलाव होने पर अमान्य होना पड़ेगा.
- पैरललिज़्म. फ़ंक्शन एक-दूसरे के साथ इंटरैक्ट करने के लिए, डिपेंडेंसी का अनुरोध करके ही इंटरैक्ट कर सकते हैं. इसलिए, एक-दूसरे पर निर्भर न रहने वाले फ़ंक्शन साथ-साथ चलाए जा सकते हैं. Bazel इस बात की गारंटी दे सकता है कि नतीजे ठीक वैसे ही मिलेंगे जैसे उन्हें क्रम से चलाने पर मिलते.
बढ़ोतरी
फ़ंक्शन, इनपुट डेटा को सिर्फ़ अन्य नोड के आधार पर ऐक्सेस कर सकते हैं. इसलिए, Bazel, इनपुट फ़ाइलों से आउटपुट फ़ाइलों तक का पूरा डेटा फ़्लो ग्राफ़ बना सकता है. इस जानकारी का इस्तेमाल, सिर्फ़ उन नोड को फिर से बनाने के लिए कर सकता है जिन्हें असल में फिर से बनाए जाने की ज़रूरत है: बदली गई इनपुट फ़ाइलों के सेट का रिवर्स ट्रांज़िटिव क्लोज़र.
खास तौर पर, बढ़ोतरी की दो संभावित रणनीतियां मौजूद हैं: पहली, बॉटम-अप या टॉप-डाउन. इनमें से कौनसा सबसे बेहतर है, यह इस बात पर निर्भर करता है कि डिपेंडेंसी ग्राफ़ कैसा दिखता है.
बॉटम-अप अमान्यता के दौरान, ग्राफ़ बनने और बदले गए इनपुट का सेट पता चलने के बाद, सभी नोड अमान्य हो जाते हैं. ये सभी नोड, बदली गई फ़ाइलों पर पूरी तरह निर्भर होते हैं. यह तब बेहतर होता है, जब एक ही टॉप-लेवल नोड को फिर से बनाया जाए. ध्यान दें कि बॉटम-अप अमान्यता के लिए, पिछले बिल्ड की सभी इनपुट फ़ाइलों पर
stat()
चलाना ज़रूरी होता है, ताकि यह पता लगाया जा सके कि उन्हें बदला गया है या नहीं. बदली गई फ़ाइलों के बारे में जानने के लिए,inotify
या इससे मिलते-जुलते तरीके का इस्तेमाल करके, इसमें सुधार किया जा सकता है.टॉप-डाउन अमान्य होने के दौरान, टॉप-लेवल नोड के ट्रांज़िटिव क्लोज़र की जांच की जाती है और सिर्फ़ उन नोड को रखा जाता है जिनका ट्रांज़िटिव क्लोज़िंग साफ़ है. यह तब बेहतर होता है, जब नोड ग्राफ़ बड़ा हो, लेकिन अगले बिल्ड में सिर्फ़ एक छोटे सबसेट की ज़रूरत हो: बॉटम-अप अमान्य करने से, पहले बिल्ड का बड़ा ग्राफ़ अमान्य हो जाएगा. वहीं, टॉप-डाउन अमान्य करने से दूसरे बिल्ड के छोटे ग्राफ़ को दिखाया जाता है.
Bazel सिर्फ़ बॉटम-अप अमान्य करता है.
ज़्यादा बढ़ोतरी पाने के लिए, Bazel चेंज प्रनिंग का इस्तेमाल करता है: अगर किसी नोड की वैल्यू अमान्य है, लेकिन उसे फिर से बनाया जाता है, तो पता चलता है कि उसकी नई वैल्यू, उसकी पुरानी वैल्यू से मेल खाती है. ऐसे में, जो नोड इस नोड में बदलाव की वजह से अमान्य हो गए थे उन्हें "फिर से बनाया गया" हो जाता है.
यह मददगार है, उदाहरण के लिए, अगर कोई व्यक्ति C++ फ़ाइल में किसी टिप्पणी में बदलाव करता है: तो इससे जनरेट की गई .o
फ़ाइल वैसी ही रहेगी. इसलिए, लिंकर को फिर से कॉल करना ज़रूरी नहीं है.
इंक्रीमेंटल लिंक / कंपाइलेशन
इस मॉडल की मुख्य सीमा यह है कि नोड को गलत तरीके से अमान्य किया जा सकता है: यह ऐसा कुछ उदाहरण हो सकता है जहां यह मददगार हो सकता है:
- इंक्रीमेंटल लिंकिंग
- जब किसी JAR फ़ाइल में एक क्लास वाली फ़ाइल बदली जाती है, तो JAR फ़ाइल को फिर से बनाने के बजाय, उसमें बदलाव किया जा सकता है.
बेज़ेल के इन चीज़ों को सिद्धांत के तौर पर समर्थन न देने की दो वजहें हैं:
- परफ़ॉर्मेंस में सीमित बढ़ोतरी हुई.
- यह पुष्टि करने में मुश्किल होती है कि बदलाव (म्यूटेशन) का नतीजा वही है जो क्लीन रीबिल्ड के लिए होता है. साथ ही, Google वैल्यू ऐसे बिल्ड होते हैं जिन्हें थोड़ा-बहुत दोहराया जा सकता है.
अब तक, महंगे बिल्ड चरण को शामिल करके अच्छी परफ़ॉर्मेंस हासिल की जा सकती थी और उस तरीके से आंशिक तौर पर फिर से आकलन किया जा सकता था. उदाहरण के लिए, किसी Android ऐप्लिकेशन में, सभी क्लास को अलग-अलग ग्रुप में बांटा जा सकता है और उन्हें अलग-अलग किया जा सकता है. इस तरह, अगर किसी ग्रुप में क्लास में कोई बदलाव नहीं होता है, तो डेक्सिंग को फिर से करने की ज़रूरत नहीं होती.
Bazel के कॉन्सेप्ट को मैप करना
यह उन SkyFunction
और SkyValue
कुंजी के बारे में खास जानकारी देता है जो Bazel, बिल्ड को पूरा करने के लिए इस्तेमाल करता है:
- FileStateValue. एक
lstat()
का नतीजा. यह फ़ंक्शन, मौजूद फ़ाइलों में हुए बदलावों का पता लगाने से जुड़ी ज़्यादा जानकारी का भी पता लगाता है. यह Skyframe ग्राफ़ में सबसे कम लेवल का नोड है और इसकी कोई डिपेंडेंसी नहीं है. - FileValue. इसका इस्तेमाल ऐसी किसी भी चीज़ के लिए किया जाता है जो
किसी फ़ाइल के असल कॉन्टेंट या समाधान किए गए पाथ की परवाह करती है. इससे जुड़े
FileStateValue
और ऐसे किसी भी सिमलिंक पर निर्भर करता है जिसका समाधान करना है. जैसे,a/b
के लिएFileValue
कोa
के समाधान किए गए पाथ औरa/b
के समाधान किए गए पाथ की ज़रूरत होती है.FileValue
और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 में शामिल होती है. यह इस सिद्धांत के उलट है कि SkyKeys छोटी होनी चाहिए. ध्यान दें कि अगर एक्ज़ीक्यूट करने का चरण नहीं चलता है, तोActionExecutionValue
औरArtifactValue
का इस्तेमाल नहीं होगा.
विज़ुअल सहायता के तौर पर, इस डायग्राम में Bazel के बनने के बाद, SkyFunction को लागू करने के बीच के संबंध को दिखाया गया है: