इस दस्तावेज़ में कोडबेस की जानकारी दी गई है. साथ ही, यह बताया गया है कि बैज़ल कैसे बनाया जाता है. इसका मकसद उन लोगों के लिए है जो Bazel में योगदान देना चाहते हैं, न कि असली उपयोगकर्ताओं के लिए.
सुविधा के बारे में जानकारी
Bazel का कोडबेस बड़ा (~350KLOC प्रोडक्शन कोड और ~260 KLOC टेस्ट कोड) है. हालांकि, कोई भी व्यक्ति पूरे लैंडस्केप को नहीं जानता है: सभी लोग अपनी खास घाटी के बारे में बहुत अच्छी तरह जानते हैं, लेकिन जानते हैं कि हर दिशा में पहाड़ों के बीच क्या है.
इस सफ़र के बीच में लोग रास्ते की जो जानकारी ढूंढते हैं, उसके हिसाब से जंगल के अंधेरे में खुद को ढूंढ न सकें, तो यह दस्तावेज़ कोड बेस की खास जानकारी देता है. इससे, उस पर काम करना आसान हो जाता है.
Bazel के सोर्स कोड का सार्वजनिक वर्शन, GitHub.com/bazelbuild/bazel पर GitHub पर रहता है. यह "सच्चाई का स्रोत" नहीं है, बल्कि यह Google के आंतरिक स्रोत ट्री से लिया गया है, जिसमें ऐसी अतिरिक्त सुविधा है जो Google के बाहर काम की नहीं है. लंबे समय तक चलने वाले लक्ष्य का मतलब है, GitHub को सच्चाई का स्रोत बनाना.
योगदानों को GitHub के अनुरोध करने के सामान्य तरीके से स्वीकार किया जाता है. इसके अलावा, कोई Googler अंदरूनी स्रोत ट्री पर मैन्युअल तरीके से इंपोर्ट करके, फिर से GitHub पर एक्सपोर्ट करता है.
क्लाइंट/सर्वर आर्किटेक्चर
Bazel का ज़्यादातर हिस्सा, सर्वर की उस प्रोसेस में मौजूद होता है जो बिल्ड के बीच रैम में मौजूद रहता है. इससे बेज़ल, बिल्ड के बीच स्थिति बनाए रख पाता है.
यही वजह है कि Bazel कमांड लाइन के दो विकल्प होते हैं: स्टार्टअप और कमांड. इस तरह के कमांड लाइन में:
bazel --host_jvm_args=-Xmx8G build -c opt //foo:bar
कुछ विकल्प (--host_jvm_args=
) दिए जाने वाले कमांड के नाम से पहले होते हैं; और कुछ -c opt
के बाद होते हैं; पुराने टाइप को "स्टार्टअप विकल्प" कहा जाता है और सर्वर पूरा प्रोसेस प्रभावित करता है, जबकि बाद वाला टाइप "कमांड विकल्प" सिर्फ़ एक निर्देश पर असर डालता है.
हर सर्वर इंस्टेंस में एक ही सोर्स ट्री ("फ़ाइल फ़ोल्डर") और हर एक फ़ाइल फ़ोल्डर में आम तौर पर एक ही सर्वर इंस्टेंस होता है. इसे एक कस्टम आउटपुट बेस तय करके बायपास किया जा सकता है (ज़्यादा जानकारी के लिए "डायरेक्ट्री लेआउट" सेक्शन देखें).
Bazel को एक ईएलएफ़ एक्ज़ीक्यूटेबल के तौर पर बांटा गया है, जो एक मान्य .zip फ़ाइल भी है.
bazel
टाइप करने पर, ऊपर दिए गए ईएलएफ़ एक्ज़ीक्यूटेबल को, C++ ("क्लाइंट") में लागू किया जा सकता है. यह इन तरीकों का इस्तेमाल करके, एक सही सर्वर प्रोसेस सेट अप करता है:
- यह जांच करता है कि क्या यह अपने-आप निकाला जा चुका है. अगर ऐसा नहीं है, तो यह काम करता है. सर्वर लागू करने का काम यहीं से होता है.
- यह पता लगाता है कि कोई सर्वर चालू है या नहीं: यह चल रहा है या नहीं, इसमें
शुरुआत करने के सही विकल्प और सही फ़ाइल फ़ोल्डर डायरेक्ट्री का इस्तेमाल किया जाता है. यह रनिंग सर्वर की खोज करने के लिए, डायरेक्ट्री
$OUTPUT_BASE/server
की खोज करती है. इस डायरेक्ट्री में एक लॉक फ़ाइल होती है जिस पर सर्वर सुन रहा होता है. - ज़रूरत पड़ने पर, यह पुराने सर्वर प्रोसेस को बंद कर देता है
- ज़रूरत पड़ने पर, सर्वर की नई प्रोसेस शुरू की जाती है
सही सर्वर प्रोसेस तैयार होने के बाद, चालू किए जाने वाले निर्देश को
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 वर्शन को इंस्टॉल के आधार पर, चेकसम के आधार पर एक सबडायरेक्ट्री मिलती है. यह डिफ़ॉल्ट रूप से $OUTPUT_USER_ROOT/install
पर होता है. इसे
--install_base
कमांड लाइन विकल्प का इस्तेमाल करके बदला जा सकता है.
"आउटपुट बेस" वह जगह है जहां बेज़ल इंस्टेंस, किसी खास फ़ाइल फ़ोल्डर में अटैच किया गया है. हर आउटपुट बेस में, बैज़ल सर्वर का ज़्यादा से ज़्यादा एक इंस्टेंस होता है, जो किसी भी समय चल सकता है. आम तौर पर, यह $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 सर्वर नियंत्रित हो जाता है और उसे एक निर्देश दिया जाता है, जिसे लागू करने की ज़रूरत होती है, तो इवेंट का यह क्रम होता है:
BlazeCommandDispatcher
को नए अनुरोध के बारे में जानकारी दी गई है. इससे यह तय होता है कि कमांड को चलाने के लिए फ़ाइल फ़ोल्डर की ज़रूरत है या नहीं (करीब-करीब हर निर्देश, लेकिन उन निर्देशों को छोड़कर जिनके पास सोर्स कोड से कोई लेना-देना नहीं है, जैसे कि वर्शन या सहायता) और क्या कोई दूसरा निर्देश चल रहा है.सही निर्देश मिलता है. हर निर्देश में इंटरफ़ेस लागू होना ज़रूरी है
BlazeCommand
और उसका@Command
एनोटेशन होना ज़रूरी है; यह एक विरोध का नमूना है, अगर एक मेटाडेटा की ज़रूरत सभी मेटाडेटा कोBlazeCommand
में बताए गए तरीकों से बताया जाता है, तो यह अच्छा होगाकमांड लाइन के विकल्पों को पार्स कर दिया गया है. हर निर्देश में अलग कमांड लाइन विकल्प होते हैं, जिनके बारे में
@Command
एनोटेशन में बताया गया है.इवेंट बस बनाई गई. इवेंट बस, उन इवेंट की एक स्ट्रीम है जो बिल्ड के दौरान होते हैं. दुनिया को यह बताने के लिए कि बिल्ड कैसे होता है इनमें से कुछ को बिल्ड इवेंट प्रोटोकॉल के तहत बैज़ल के बाहर एक्सपोर्ट किया जाता है.
इससे कमांड को कंट्रोल मिल जाता है. सबसे दिलचस्प निर्देश वे होते हैं जो बिल्ड: बिल्ड, टेस्ट, रन, कवरेज वगैरह चलाते हैं: यह फ़ंक्शन
BuildTool
से लागू किया जाता है.कमांड लाइन के टारगेट पैटर्न के सेट को पार्स किया जाता है. साथ ही,
//pkg:all
और//pkg/...
जैसे वाइल्डकार्ड का समाधान किया जाता है. इसेAnalysisPhaseRunner.evaluateTargetPatterns()
में लागू किया जाता है. साथ ही, इसे स्काईफ़्रेमTargetPatternPhaseValue
में बदला जाता है.लोडिंग/विश्लेषण का चरण, कार्रवाई ग्राफ़ (बिल्ड के लिए एक्ज़ीक्यूट किया गया निर्देश वाला एसाइकलिक ग्राफ़) बनाने के लिए चलाया जाता है.
एक्ज़ीक्यूशन का चरण पूरा होता है. इसका मतलब है कि अनुरोध किए गए टॉप-लेवल टारगेट को बनाने के लिए, हर ज़रूरी कार्रवाई को चलाना होगा.
कमांड लाइन के विकल्प
बेज़ल कॉल के लिए कमांड लाइन के विकल्पों के बारे में OptionsParsingResult
ऑब्जेक्ट में बताया गया है. ऐसा करने पर, विकल्पों की वैल्यू में "क्लास" का मैप शामिल होता है. "विकल्प क्लास", OptionsBase
की सब-क्लास है और ग्रुप कमांड लाइन के विकल्प एक-दूसरे से जुड़े हैं. उदाहरण के लिए:
- प्रोग्रामिंग भाषा (
CppOptions
याJavaOptions
) से जुड़े विकल्प. येFragmentOptions
का सब-क्लास होना चाहिए और आखिर में इसेBuildOptions
ऑब्जेक्ट में रैप कर दिया जाता है. - बेज़ल के काम करने के तरीके से जुड़े विकल्प (
ExecutionOptions
)
इन विकल्पों को विश्लेषण के चरण में इस्तेमाल करने के लिए डिज़ाइन किया गया है. इन्हें Java में RuleContext.getFragment()
या स्टारलार्क में ctx.fragments
की मदद से डिज़ाइन किया गया है.
उनमें से कुछ (उदाहरण के लिए, C++ में स्कैन करना शामिल है या नहीं) को एक्ज़ीक्यूशन के चरण में पढ़ा जाता है या नहीं, लेकिन इसके लिए हमेशा साफ़ तौर पर प्लंबिंग की ज़रूरत होती है, क्योंकि उस समय BuildConfiguration
उपलब्ध नहीं होता है. ज़्यादा जानकारी के लिए, "कॉन्फ़िगरेशन" सेक्शन देखें.
चेतावनी: हम यह बताना चाहते हैं कि OptionsBase
इंस्टेंस में बदलाव नहीं होता और उनका इस्तेमाल इसी तरीके से किया जाता है, जैसे कि SkyKeys
का हिस्सा. हालांकि, ऐसा नहीं है और
बेज़ल में बदलाव करने के तरीके को डीबग करना मुश्किल होता है. हमें अफ़सोस है कि हमने उन्हें असल में अचल बना दिया है. ऐसा करना एक बड़ी कोशिश है.
(किसी और के बनने से ठीक पहले FragmentOptions
में बदलाव करने से, उसका रेफ़रंस रखने का मौका मिलता है. साथ ही, equals()
या hashCode()
को कॉल करने से पहले ठीक है.)
Bazel इन तरीकों से विकल्प क्लास के बारे में बताता है:
- कुछ डिवाइसों को बैज़ल (
CommonCommandOptions
) में तार वाले डिवाइसों से कनेक्ट किया गया है - हर Bazel कमांड पर
@Command
एनोटेशन से ConfiguredRuleClassProvider
से (और ये अलग-अलग प्रोग्रामिंग भाषाओं से जुड़े कमांड लाइन विकल्प हैं)- Starlark के नियम भी अपने विकल्प तय कर सकते हैं. इसके लिए, यहां जाएं
हर विकल्प (Starlark के तय किए गए विकल्पों को छोड़कर) एक ऐसे FragmentOptions
सब क्लास का सदस्य वैरिएबल है जिसमें @Option
एनोटेशन होता है. इसमें, कुछ सहायता टेक्स्ट के साथ नाम और कमांड लाइन का टाइप की जानकारी दी जाती है.
आम तौर पर, कमांड लाइन विकल्प की वैल्यू का जावा टाइप कुछ आसान होता है (जैसे कि कोई स्ट्रिंग, पूर्णांक, बूलियन, लेबल वगैरह). हालांकि, हम कुछ और तरह के मुश्किल विकल्पों को भी शामिल करने की सुविधा देते हैं. इस मामले में, कमांड लाइन स्ट्रिंग से डेटा टाइप में बदलने का काम, लागू होने वाले com.google.devtools.common.options.Converter
में हो जाता है.
बेज़ल के नज़ारों वाला सोर्स ट्री
बेज़ल, सॉफ़्टवेयर बनाने का कारोबार करता है. यह सॉफ़्टवेयर, सोर्स कोड को पढ़कर और उसे पढ़कर बताया जाता है. Bazel से इस्तेमाल किए जाने वाले सोर्स कोड की कुल संख्या को "फ़ाइल फ़ोल्डर" कहा जाता है. इसे डेटा स्टोर करने की जगह, पैकेज, और नियमों में व्यवस्थित किया जाता है.
डेटा स्टोर करने की जगह
"डेटा स्टोर करने की जगह" एक ऐसा सोर्स ट्री है, जिस पर डेवलपर काम करता है. आम तौर पर, यह एक ही प्रोजेक्ट को दिखाता है. बज़ेल के पूर्वज, ब्लेज़ एक मोनोरेपो पर ऑपरेट करते थे. इसका मतलब है कि सिंगल सोर्स ट्री में बिल्ड को चलाने के लिए इस्तेमाल होने वाले सभी सोर्स कोड शामिल होते हैं. वहीं, Bazel से उन प्रोजेक्ट को भी मदद मिलती है जिनका सोर्स कोड एक से ज़्यादा रिपॉज़िटरी (डेटा स्टोर करने की जगह) में मौजूद है. जिस रिपॉज़िटरी से बेज़ल को शुरू किया गया है उसे "मुख्य रिपॉज़िटरी" कहा जाता है. बाकी डेटा स्टोर करने की जगहों को "रिपॉज़िटरी" कहा जाता है.
डेटा स्टोर करने की जगह को इसकी रूट डायरेक्ट्री में WORKSPACE
(या WORKSPACE.bazel
) नाम से मार्क किया जाता है. इस फ़ाइल में पूरी दुनिया के लिए "ग्लोबल" जानकारी शामिल होती है, उदाहरण के लिए, उपलब्ध बाहरी डेटा स्टोर करने की जगहों का सेट. यह सामान्य Starlark फ़ाइल
की तरह काम करता है. इसका मतलब है कि कोई भी load()
अन्य Starlark फ़ाइलें बना सकता है.
आम तौर पर, इसका इस्तेमाल रिपॉज़िटरी (डेटा स्टोर की जगह) में करने के लिए किया जाता है. "रिपॉज़िटरी (डेटा स्टोर करने की जगह)" में इसका इस्तेमाल करना ज़रूरी है. हम इसे "deps.bzl
पैटर्न" कहते हैं
डेटा स्टोर करने की बाहरी जगह का कोड,
$OUTPUT_BASE/external
के तहत सिम्युलेट या डाउनलोड किया जाता है.
बिल्ड करते समय, पूरे सोर्स ट्री को एक साथ रखना होगा (SymlinkForest
$EXECROOT
$EXECROOT/external
$EXECROOT/..
external
पैकेज
हर रिपॉज़िटरी, पैकेज से जुड़ी फ़ाइलों, और डिपेंडेंसी के बारे में
बताया जाता है. ये चीज़ें BUILD
या BUILD.bazel
नाम की फ़ाइल से तय की जाती हैं. अगर दोनों मौजूद हैं, तो बेज़ल BUILD.bazel
को प्राथमिकता देती हैं. BUILD
फ़ाइलों के अब भी स्वीकार किए जाने की वजह यह है कि बैजल के पूर्वज, ब्लेज़ ने इस फ़ाइल नाम का इस्तेमाल किया था. हालांकि, पहले यह एक खास तरह का पाथ सेगमेंट हुआ करता था, खास तौर पर Windows पर, जहां फ़ाइल के नाम केस-इनसेंसिटिव होते हैं.
पैकेज एक-दूसरे से अलग होते हैं: पैकेज की BUILD
फ़ाइल में बदलाव करने से
दूसरे पैकेज में बदलाव नहीं हो सकता. BUILD
फ़ाइलों को जोड़ने या हटाने के लिए
_अन्य पैकेज में बदलाव किया जा सकता है, क्योंकि बार-बार किए जाने वाले ग्लोब, पैकेज की सीमाओं पर रुक जाते हैं,
और इसलिए BUILD
फ़ाइल की मौजूदगी बार-बार होने वाली प्रक्रिया को रोक देती है.
BUILD
फ़ाइल के मूल्यांकन का नाम "पैकेज लोड होना" है. इसे PackageFactory
क्लास में लागू किया जाता है. यह Starlark अनुवादक को कॉल करता है और इसके लिए उपलब्ध नियम क्लास के सेट के बारे में जानकारी होना ज़रूरी है. पैकेज लोड होने का नतीजा
Package
ऑब्जेक्ट होता है. यह ज़्यादातर टारगेट (किसी टारगेट का नाम) से टारगेट में मैप होता है.
पैकेज लोड होने के दौरान जटिलता का एक बड़ा हिस्सा ग्लोब है: Bazel पर हर सोर्स फ़ाइल को साफ़ तौर पर सूची में रखने की ज़रूरत नहीं होती. इसके बजाय, वह ग्लोब (जैसे कि glob(["**/*.java"])
) चला सकता है. शेल के उलट, यह घुमक्कड़ ग्लोब का इस्तेमाल करता है जो सबडायरेक्ट्री में जाता है (सबपैकेज में नहीं). इसके लिए फ़ाइल सिस्टम का ऐक्सेस होना ज़रूरी है, इसलिए यह बहुत धीमा हो सकता है. इसलिए, हम सभी तरीकों को लागू करते हैं, ताकि इन्हें साथ-साथ और जितना संभव हो उतना बेहतर तरीके से चलाया जा सके.
ग्लोबिंग को इन क्लास में लागू किया जाता है:
LegacyGlobber
तेज़ और खुशहाल स्काईफ़्रेम-अनवेयर ग्लोबरSkyframeHybridGlobber
एक वर्शन, जो Skyframe का इस्तेमाल करता है और "Skyframe के फिर से शुरू होने" से बचने के लिए, पुराने ग्लोबर पर वापस जाता है. इसके बारे में नीचे बताया गया है
Package
क्लास में कुछ ऐसे सदस्य भी होते हैं जिनका इस्तेमाल खास तौर पर WORKSPACE फ़ाइल को पार्स करने के लिए होता है और जो असल पैकेज के लिए सही नहीं होता. यह एक डिज़ाइन की गड़बड़ी है, क्योंकि सामान्य पैकेज का ब्यौरा देने वाले ऑब्जेक्ट में
फ़ील्ड नहीं होने चाहिए, जो किसी और चीज़ की जानकारी देता हो. इन देशों या इलाकों में शामिल हैं:
- डेटा स्टोर करने की जगह की मैपिंग
- रजिस्टर किए गए टूलचेन
- रजिस्टर किए गए एक्ज़ीक्यूशन प्लैटफ़ॉर्म
आम तौर पर, सामान्य पैकेज को पार्स करने के बाद WORKSPACE फ़ाइल को पार्स करने के बीच ज़्यादा अंतर होगा, ताकि Package
को दोनों की ज़रूरतों को पूरा करने की ज़रूरत न हो. माफ़ करें, ऐसा करना मुश्किल है, क्योंकि दोनों एक-दूसरे से जुड़े हुए हैं.
लेबल, टारगेट, और नियम
पैकेज, टारगेट से बने होते हैं. इनके टाइप नीचे दिए गए हैं:
- फ़ाइलें: ऐसी चीज़ें जो इनपुट या बिल्ड का आउटपुट होती हैं. {0} बाजेल पार्लर में, हम इन्हें आर्टफ़ैक्ट कहते हैं. इस बारे में कहीं और बातचीत की गई है. इस बिल्ड के दौरान बनाई गई सभी फ़ाइलें, टारगेट नहीं होती हैं. आम तौर पर, Ba इतने आउटपुट के लिए उससे जुड़ा कोई लेबल नहीं होता है.
- नियम: ये इनपुट के आउटपुट से नतीजे पाने के तरीके की जानकारी देते हैं. आम तौर पर, ये प्रोग्रामिंग भाषा (जैसे कि
cc_library
,java_library
याpy_library
) से जुड़े होते हैं, लेकिन भाषा की कोई भाषा नहीं होती (जैसे किgenrule
याfilegroup
) - पैकेज ग्रुप: इनकी जानकारी किसको दिखे सेक्शन में दी गई है.
टारगेट के नाम को लेबल कहा जाता है. लेबल का सिंटैक्स
@repo//pac/kage:name
है. repo
उस रिपॉज़िटरी का नाम है जिसमें
लेबल है; pac/kage
वह डायरेक्ट्री है जिसमें BUILD
फ़ाइल मौजूद है और name
फ़ाइल के पाथ का
लेबल है (अगर लेबल, सोर्स के रेफ़रंस का रेफ़रंस देता है). कमांड लाइन पर किसी टारगेट के बारे में बताते समय, लेबल के कुछ हिस्सों को हटाया जा सकता है:
- अगर डेटा स्टोर करने की जगह को खाली कर दिया जाता है, तो लेबल को मुख्य डेटा स्टोर करने की जगह पर ले जाया जाता है.
- (अगर पैकेज का कोई हिस्सा शामिल नहीं है (जैसे कि
name
या:name
), तो लेबल को मौजूदा वर्किंग डायरेक्ट्री के पैकेज में ले जाया जाता है (ऐसे रिलेटिव पाथ जिनमें अपलेवल रेफ़रंस (..) होते हैं) की अनुमति नहीं है
नियम "C++ लाइब्रेरी" जैसे नियम को "नियम क्लास" कहा जाता है. नियम की क्लास को Starlark (rule()
फ़ंक्शन) या Java (यानी "नेटिव नियम", टाइप RuleClass
) में लागू किया जा सकता है. लंबी अवधि के लिए, हर भाषा के लिए खास नियम Starlark में लागू किया जाएगा, लेकिन कुछ पुराने नियम वाले परिवार (जैसे कि Java या C++) कुछ समय के लिए Java में बने रहेंगे.
Starlark नियम की क्लास को load()
फ़ाइलों का इस्तेमाल करके, BUILD
फ़ाइलों की शुरुआत में इंपोर्ट किया जाना चाहिए. वहीं, Java नियम की क्लास को ConfiguredRuleClassProvider
नियम वाली क्लास में यह जानकारी शामिल होती है:
- इसके एट्रिब्यूट (जैसे,
srcs
,deps
): उनके टाइप, डिफ़ॉल्ट वैल्यू, सीमाएं, वगैरह. - कॉन्फ़िगरेशन ट्रांज़िशन और हर एट्रिब्यूट से जुड़े आसपेक्ट रेशियो (अगर कोई हो)
- नियम लागू करना
- वह ट्रांज़िट सेवा देने वाली कंपनी जिसके बारे में "आम तौर पर" जानकारी दी जाती है
शब्दावली नोट: कोडबेस में, हम अक्सर नियम नियम की वजह से बनाए गए टारगेट को "नियम" बनाने के लिए इस्तेमाल करते हैं. Starlark और उपयोगकर्ता को दिखने वाले दस्तावेज़ों में,
"नियम" का इस्तेमाल खास तौर पर नियम क्लास के लिए किया जाना चाहिए, जबकि टारगेट सिर्फ़ "टारगेट" है. यह भी ध्यान रखें कि RuleClass
के नाम में "क्लास" होने के बावजूद, नियम की क्लास और उस तरह के टारगेट के बीच कोई Java इनहेरिटेंस संबंध नहीं है.
स्काईफ़्रेम
Bazel में मौजूद आकलन फ़्रेमवर्क को स्काईफ़्रेम कहा जाता है. इसका मॉडल यह है कि किसी भी चीज़ को बनाने के दौरान जिस भी चीज़ को बनाने की ज़रूरत होती है वह एक सीधे तौर पर मौजूद
ग्राफ़ में नोड को SkyValue
और उनके नाम को SkyKey
कहा जाता है. दोनों ही पूरी तरह से नहीं बदले जा सकने वाले हैं. सिर्फ़ ऐसे ऑब्जेक्ट को ही ऐक्सेस किया जा सकता है जिनमें बदलाव नहीं किया जा सकता है. यह वैरिएंट करीब-करीब हमेशा लागू होता है और अगर यह ऐसा नहीं होता है, तो हम पूरी कोशिश करते हैं कि BuildOptions
को न बदलें और न ही उसे सिर्फ़ ऐसे तरीकों से बदलें जो बाहरी हिस्से के ज़रिए निगरानी न किए जा सकें.BuildConfigurationValue
SkyKey
इससे यह पक्का होता है कि Skyframe में कॉन्फ़िगर की गई हर चीज़ (जैसे, कॉन्फ़िगर किए गए टारगेट) में भी बदलाव नहीं हो सकता.
Skyframe के ग्राफ़ को देखने का सबसे आसान तरीका है कि bazel dump
--skyframe=deps
को ग्राफ़ में डाला जाए. इससे एक लाइन में एक SkyValue
मिट जाता है. छोटे-छोटे बिल्ड के लिए ऐसा करना सबसे अच्छा होता है, क्योंकि यह बहुत बड़ा हो सकता है.
स्काईफ़्रेम, com.google.devtools.build.skyframe
पैकेज में मौजूद है. मिलते-जुलते नाम वाले पैकेज com.google.devtools.build.lib.skyframe
में स्काईफ़्रेम के ऊपर बेज़ल लागू किया गया है. Skyframe के बारे में ज़्यादा जानकारी यहां उपलब्ध है.
SkyKey
के लिए SkyValue
का आकलन करने के लिए, स्काईफ़्रेम की कुंजी के हिसाब से SkyFunction
शुरू करेगा. फ़ंक्शन के आकलन के दौरान, यह स्काईफ़्रेम से अन्य डिपेंडेंसी का अनुरोध कर सकता है. इसके लिए, SkyFunction.Environment.getValue()
के अलग-अलग ओवरलोड को कॉल करना होता है. इससे स्काईफ़्रेम के इंटरनल ग्राफ़ में उन डिपेंडेंसी को रजिस्टर करने का खराब असर पड़ता है. इससे स्काईफ़्रेम को पता चलता है कि उसकी किसी डिपेंडेंसी में बदलाव होने पर, उसे फिर से आकलन करना है. दूसरे शब्दों में, Skyframe की कैश मेमोरी और कंप्यूटेशन का पता लगाने की सुविधा, SkyFunction
s और SkyValue
s के स्तर पर काम करती है.
जब भी SkyFunction
किसी ऐसी डिपेंडेंसी के लिए अनुरोध करता है जो उपलब्ध नहीं है, तो getValue()
शून्य दिखेगा. इसके बाद, फ़ंक्शन को अपने-आप Skyframe के लिए शून्य मिल जाना चाहिए. कुछ समय बाद, स्काईफ़्रेम उपलब्ध न होने पर डिपेंडेंसी का आकलन करेगा. इसके बाद, शुरुआत से फ़ंक्शन को रीस्टार्ट करेगा — सिर्फ़ इस बार, getValue()
कॉल बिना किसी शून्य वाले नतीजे के साथ सफल होगा.
ऐसे में, रीस्टार्ट होने से पहले
SkyFunction
में की गई सभी कंप्यूट को दोहराया जाना चाहिए. हालांकि, इसमें डिपेंडेंसी SkyValues
की जांच करने के लिए किए गए काम शामिल नहीं हैं. कैश मेमोरी को कैश मेमोरी में सेव किया जाता है. इसलिए, हम आम तौर पर इस समस्या को यह तरीके से हल करते हैं:
- रीस्टार्ट की संख्या सीमित करने के लिए, बैच में डिपेंडेंसी तय करना (
getValuesAndExceptions()
का इस्तेमाल करके). SkyValue
को अलग-अलग हिस्सों में बांटकर, अलग-अलगSkyFunction
के हिसाब से लगाया जाता है, ताकि उनका अलग-अलग हिसाब लगाया जा सके और उन्हें कैश मेमोरी में सेव किया जा सके. यह इसलिए किया जाना चाहिए, क्योंकि इससे मेमोरी इस्तेमाल करने की संभावना बढ़ जाती है.- रीस्टार्ट करने के बीच,
SkyFunction.Environment.getState()
का इस्तेमाल करके या ऐड-हॉक स्टैटिक कैश मेमोरी का इस्तेमाल करके, "Skyframe के पीछे" सेव किया जाता है.
मूल रूप से, हमें इन तरीकों की ज़रूरत होती है, क्योंकि हमारे पास हज़ारों हज़ारों इन-फ़्लाइट स्काईफ़्रेम नोड हैं, और Java में लाइटवेट थ्रेड काम नहीं करते.
स्टारार्क
Starlark, डोमेन की खास भाषा है, जिसका इस्तेमाल लोग बेज़ल को कॉन्फ़िगर करने और बढ़ाने के लिए करते हैं. इसे Python के प्रतिबंधित सबसेट के तौर पर माना जाता है. इसमें बहुत कम टाइप होते हैं, कंट्रोल फ़्लो पर ज़्यादा पाबंदियां होती हैं, और सबसे ज़रूरी, एक साथ काम करने वाले रीड को चालू करने की मज़बूत असमानता. यह ट्यूरिंग-कंप्लीट नहीं है, जो भाषा के कुछ सामान्य प्रोग्रामिंग टास्क पूरे करने से कुछ उपयोगकर्ताओं को परेशान करता है.
Starlark को net.starlark.java
पैकेज में लागू किया गया है.
इसे लागू करने के लिए एक अलग तरीका यहां भी है. फ़िलहाल,
Bzel में इस्तेमाल किए गए Java को लागू करने के लिए, अनुवादक उपलब्ध है.
Starlark का इस्तेमाल कई संदर्भ में किया जाता है. इनमें ये शामिल हैं:
BUILD
भाषा. यहां नए नियम तय होते हैं. इस कॉन्टेक्स्ट में चलने वाले Starlark कोड के पास सिर्फ़BUILD
फ़ाइल के कॉन्टेंट और इसके लोड किए गए.bzl
फ़ाइलों का ऐक्सेस होता है.- नियम की परिभाषाएं. इस तरह से, नए नियमों (जैसे कि नई भाषा के लिए सहायता) को तय किया जाता है. इस कॉन्टेक्स्ट में चलने वाले Starlark कोड के पास, इसके डायरेक्ट डिपेंडेंसी से मिले कॉन्फ़िगरेशन और डेटा का ऐक्सेस होता है. बाद में, इस बारे में ज़्यादा जानकारी देखी जा सकती है.
- Workspace फ़ाइल. यहीं पर बाहरी रिपॉज़िटरी (कोड, मुख्य सोर्स ट्री में नहीं है) के बारे में बताया जाता है.
- डेटा स्टोर करने की जगह के नियम की परिभाषाएं. यहीं पर नया बाहरी रिपॉज़िटरी टाइप तय किए जाते हैं. इस कॉन्टेक्स्ट में चलने वाला Starlark कोड, उस मशीन पर आर्बिट्ररी कोड चला सकता है जिस पर Bazel चल रहा है और फ़ाइल फ़ोल्डर के बाहर पहुंच सकता है.
BUILD
और .bzl
फ़ाइलों के लिए उपलब्ध बोलियां थोड़ी अलग हैं, क्योंकि ये अलग-अलग चीज़ें दिखाती हैं. अंतर की सूची यहां उपलब्ध है.
Starlark के बारे में ज़्यादा जानकारी यहां उपलब्ध है.
लोड होने का विश्लेषण करने का चरण
लोडिंग/विश्लेषण का चरण वह होता है जहां बेज़ल यह तय करती है कि कोई खास नियम बनाने के लिए क्या कार्रवाइयां करनी होंगी. इसकी बुनियादी इकाई एक "कॉन्फ़िगर किया गया टारगेट" है, जो बहुत ही समझदारी से एक (टारगेट, कॉन्फ़िगरेशन) जोड़ा है.
इसे "लोड हो रहा है/विश्लेषण का चरण" कहा जाता है, क्योंकि इसे दो अलग-अलग हिस्सों में बांटा जा सकता है. हालांकि, अब इन्हें सीरियल में बांटा गया है, लेकिन अब ये समय के साथ ओवरलैप कर सकते हैं:
- पैकेज लोड हो रहे हैं. इसका मतलब है कि
BUILD
फ़ाइलों कोPackage
ऑब्जेक्ट में बदला जा रहा है, जो इनके बारे में बताते हैं - कॉन्फ़िगर किए गए टारगेट का विश्लेषण करना, यानी कि कार्रवाई ग्राफ़ बनाने के लिए नियमों को लागू करना
कमांड लाइन पर अनुरोध किए गए कॉन्फ़िगर किए गए टारगेट की ट्रांज़िट समय वाली विंडो में, कॉन्फ़िगर किए गए हर टारगेट का विश्लेषण नीचे से ऊपर किया जाना चाहिए. इसका मतलब है कि पहले लीफ़ नोड और फिर कमांड लाइन का विश्लेषण किया जाना चाहिए. कॉन्फ़िगर किए गए एक ही टारगेट के विश्लेषण में इनपुट करते हैं:
- कॉन्फ़िगरेशन. नियम बनाने का तरीका "उदाहरण के लिए,
- डायरेक्ट डिपेंडेंसी. उनके ट्रांज़िटिव जानकारी देने वाले आंकड़े विश्लेषण किए जाने के लिए उपलब्ध हैं. उन्हें इस तरह कहा जाता है, क्योंकि वे कॉन्फ़िगर किए गए टारगेट के ट्रांज़िट समय में बंद होने वाली जानकारी को "रोल-अप" की सुविधा देते हैं. जैसे, क्लासपाथ पर मौजूद सभी . Jar फ़ाइल को या ऐसी सभी .o फ़ाइलों को जिन्हें C++ बाइनरी से लिंक करना ज़रूरी होता है)
- टारगेट खुद होता है. ऐसा पैकेज लोड होने के बाद होता है, जिसमें टारगेट होता है. नियमों में इसकी विशेषताएं शामिल होती हैं, जो आम तौर पर मायने रखती है.
- कॉन्फ़िगर किए गए टारगेट को लागू करना. नियमों के लिए, यह Starlark या Java में हो सकता है. बिना नियम वाले कॉन्फ़िगर किए गए सभी टारगेट, Java में लागू किए जाते हैं.
कॉन्फ़िगर किए गए टारगेट का विश्लेषण करने का आउटपुट है:
- ट्रांज़िटिव जानकारी देने वाली वे कंपनियां जो इस पर निर्भर रहने वाले टारगेट कॉन्फ़िगर करते हैं, ऐक्सेस कर सकते हैं
- कलाकृतियां और उन्हें बनाने वाली कार्रवाइयां
Java नियमों का ऑफ़र किया गया एपीआई RuleContext
है. यह Starlark के नियमों वाले ctx
आर्ग्युमेंट के बराबर है. इसका एपीआई ज़्यादा असरदार है. हालांकि, इस स्थिति में बैड थिंग्स TM को इस्तेमाल करना ज़्यादा आसान होता है. उदाहरण के लिए, कोड लिखना जिसका समय या स्पेस की जटिलता क्वाड्रेटिक है या बज़ेल सर्वर क्रैश होना, Java अपवाद को बदलना या वैरिएंट का उल्लंघन करना (जैसे कि अनजाने में किसी Options
इंस्टेंस में बदलाव करना या कॉन्फ़िगर किए जा सकने वाले किसी टारगेट को बनाना)
ऐसा एल्गोरिदम जो कॉन्फ़िगर किए गए टारगेट की डायरेक्ट डिपेंडेंसी तय करता है, वह DependencyResolver.dependentNodeMap()
में रहता है.
कॉन्फ़िगरेशन
कॉन्फ़िगरेशन, टारगेट बनाने का "तरीका" हैं: कौनसे प्लैटफ़ॉर्म के लिए, कमांड लाइन के साथ कौनसे विकल्प वगैरह.
एक ही बिल्ड में कई कॉन्फ़िगरेशन के लिए एक ही टारगेट बनाया जा सकता है. उदाहरण के लिए, यह उपयोगी है, उदाहरण के लिए, जब बिल्ड और टारगेट कोड के दौरान चलाए जाने वाले टूल के लिए एक ही कोड इस्तेमाल किया जाता है और हम एक फ़ैट Android ऐप्लिकेशन (जो कई सीपीयू आर्किटेक्चर के लिए नेटिव कोड बनाते हैं) बनाते समय क्रॉस-कंपाइल करते हैं
सैद्धान्तिक तौर पर, यह कॉन्फ़िगरेशन एक BuildOptions
इंस्टेंस होता है. हालांकि,
असल में, BuildOptions
को BuildConfiguration
से रैप किया जाता है, जिससे कई दूसरी सुविधाएं
सुविधा मिलती हैं. यह डिपेंडेंसी ग्राफ़ के ऊपर से नीचे की ओर जाता है. अगर यह बदलता है, तो बिल्ड का
विश्लेषण करना होगा.
उदाहरण के लिए, पूरे बिल्ड का फिर से विश्लेषण करने जैसी अनियमितताएं होने पर, उदाहरण के लिए, अनुरोध किए गए टेस्ट की संख्या बदल जाती है. फिर चाहे इसका असर सिर्फ़ टेस्ट टारगेट पर ही होता हो, लेकिन हमारी योजना "ट्रिम" करने के तरीके पर है, लेकिन ऐसा नहीं है.
जब किसी नियम को लागू करने के लिए कॉन्फ़िगरेशन का हिस्सा होना ज़रूरी हो, तो आपको RuleClass.Builder.requiresConfigurationFragments()
का इस्तेमाल करके इसकी परिभाषा में
यह बताना होगा. यह दोनों गलतियों से बचने के लिए होता है (जैसे कि Java फ़्रैगमेंट का इस्तेमाल करने वाले Python के नियम)
और कॉन्फ़िगरेशन की काट-छांट करने की सुविधा देना, जैसे कि अगर Python के विकल्प बदलते हैं, तो C++ के टारगेट को फिर से विश्लेषण करने की ज़रूरत नहीं होती.
किसी नियम का कॉन्फ़िगरेशन, उसके "माता-पिता" नियम के हिसाब से ज़रूरी नहीं होता. डिपेंडेंसी एज में कॉन्फ़िगरेशन बदलने की प्रक्रिया को "कॉन्फ़िगरेशन ट्रांज़िशन" कहा जाता है. ऐसा दो जगहों पर हो सकता है:
- डिपेंडेंसी एज पर. ये ट्रांज़िशन
Attribute.Builder.cfg()
में बताए गए हैं और येRule
(जहां ट्रांज़िशन होते हैं) औरBuildOptions
(ओरिजनल कॉन्फ़िगरेशन) से एक या ज़्यादाBuildOptions
(आउटपुट कॉन्फ़िगरेशन) तक के फ़ंक्शन हैं. - कॉन्फ़िगर किए गए टारगेट के किसी भी आने वाले किनारे पर. इनकी जानकारी
RuleClass.Builder.cfg()
में दी गई है.
काम की क्लास TransitionFactory
और ConfigurationTransition
हैं.
कॉन्फ़िगरेशन ट्रांज़िशन का इस्तेमाल किया जाता है, उदाहरण के लिए:
- यह बताने के लिए कि बिल्ड के दौरान किसी खास डिपेंडेंसी का इस्तेमाल किया जाता है और इसलिए, उसे एक्ज़ीक्यूशन आर्किटेक्चर में बनाया जाना चाहिए
- यह एलान करना कि एक खास डिपेंडेंसी, कई आर्किटेक्चर के लिए बनाई जानी चाहिए, जैसे कि फ़ैट Android APKs के नेटिव कोड के लिए
अगर किसी कॉन्फ़िगरेशन का ट्रांज़िशन, एक से ज़्यादा कॉन्फ़िगरेशन में किया जाता है, तो उसे स्प्लिट ट्रांज़िशन कहा जाता है.
कॉन्फ़िगरेशन ट्रांज़िशन को Starlark में भी लागू किया जा सकता है (दस्तावेज़ यहां)
ट्रांज़िटिव जानकारी देने वाले
ट्रांज़िटिव जानकारी देने वाले, कॉन्फ़िगर किए गए टारगेट का एक तरीका (_______) है जो कॉन्फ़िगर किए गए अन्य टारगेट के बारे में बताता है. उनके नाम में "हमेशा बदल जाने वाला" होने का कारण यह है कि आम तौर पर यह किसी कॉन्फ़िगर किए गए टारगेट की ट्रांज़िटिव क्लोज़िंग का रोल-अप होता है.
आम तौर पर, Java ट्रांज़िटिव जानकारी देने वाली कंपनियों और Starlark के बीच 1:1 संपर्क होता है (हालांकि, DefaultInfo
FileProvider
, FilesToRunProvider
, और RunfilesProvider
का एक तालमेल है, क्योंकि एपीआई को Java को डायरेक्ट करने वाला एक से ज़्यादा बनने वाला सर्वर माना गया).
उनकी कुंजी इनमें से एक है:
- Java क्लास ऑब्जेक्ट. यह सुविधा सिर्फ़ उन कंपनियों के लिए उपलब्ध है जिन्हें Starlark से ऐक्सेस नहीं
किया जा सकता. ये कंपनियां,
TransitiveInfoProvider
की सब-क्लास हैं. - स्ट्रिंग. यह लेगसी है और कई बार इसे खारिज कर दिया जाता है. ऐसा इसलिए, क्योंकि इस पर नाम का टकराव हो सकता है. सार्वजनिक परिवहन से जुड़ी जानकारी देने वाली ये कंपनियां, सीधे तौर पर
build.lib.packages.Info
की सब-क्लास हैं . - सेवा देने वाली कंपनी का निशान. Starlark की मदद से, इस फ़ंक्शन को
provider()
फ़ंक्शन का इस्तेमाल करके बनाया जा सकता है. हमारा सुझाव है कि आप नए प्रोवाइडर बनाएं. यह चिह्न Java में,Provider.Key
इंस्टेंस के ज़रिए दिखाया जाता है.
Java का इस्तेमाल करके लागू की गई नई कंपनियां, BuiltinProvider
का इस्तेमाल करके लागू की जानी चाहिए.
NativeProvider
के इस्तेमाल पर रोक लगा दी गई है (हमें अब तक इसे हटाने का समय नहीं मिला) और
TransitiveInfoProvider
क्लास को Starlark से ऐक्सेस नहीं किया जा सकता.
कॉन्फ़िगर किए गए टारगेट
कॉन्फ़िगर किए गए टारगेट, RuleConfiguredTargetFactory
के तौर पर लागू किए जाते हैं. Java में लागू किए गए हर नियम की क्लास के लिए एक सब-क्लास है. Starlark कॉन्फ़िगर किए गए टारगेट
StarlarkRuleConfiguredTargetUtil.buildRule()
से बनाए गए हैं .
कॉन्फ़िगर की गई टारगेट फ़ैक्ट्रियों को अपना रिटर्न
वैल्यू बनाने के लिए RuleConfiguredTargetBuilder
का इस्तेमाल करना चाहिए. इसमें ये चीज़ें शामिल हैं:
- उनकी
filesToBuild
"इस नियम वाली फ़ाइलों का सेट" को दिखाने वाला धुंधला सिद्धांत. ये फ़ाइलें तब बनती हैं, जब कॉन्फ़िगर किया गया टारगेट, कमांड लाइन पर होता है या किसी genrule के src में. - उनकी रनफ़ाइल, सामान्य, और डेटा.
- उनके आउटपुट ग्रुप. ये "फ़ाइल के अन्य सेट" हैं, जिन्हें नियम बना सकता है. इन्हें BUILD में फ़ाइलग्रुप नियम के आउटपुट_ग्रुप एट्रिब्यूट का इस्तेमाल करके या Java में
OutputGroupInfo
सेवा देने वाली कंपनी का इस्तेमाल करके ऐक्सेस किया जा सकता है.
रनफ़ाइल
कुछ बाइनरी फ़ाइलों को चलाने के लिए, डेटा फ़ाइलों की ज़रूरत होती है. एक प्रमुख उदाहरण ऐसे टेस्ट हैं जिन्हें इनपुट फ़ाइलों की ज़रूरत होती है. इसे "रनफ़ाइल" के कॉन्सेप्ट में बेज़ल में दिखाया गया है. "runfiles ट्री", किसी खास बाइनरी की डेटा फ़ाइलों का डायरेक्ट्री ट्री होता है. इसे फ़ाइल सिस्टम में एक सिमलिंक ट्री के रूप में बनाया जाता है. इसमें आउटपुट ट्री के सोर्स में मौजूद फ़ाइलों की झलक होती है.
रन फ़ाइलों के सेट को Runfiles
इंस्टेंस के तौर पर दिखाया जाता है. सैद्धांतिक रूप से यह, रनफ़ाइल ट्री में मौजूद फ़ाइल के पाथ से लेकर, Artifact
इंस्टेंस को मैप करने वाला मैप होता है. यह दो वजहों से, एक ही Map
से ज़्यादा मुश्किल लगता है:
- ज़्यादातर मामलों में, किसी फ़ाइल का रनफ़ाइल पाथ वही होता है जो उसके एक्सपेक्चर का होता है. हम कुछ रैम बचाने के लिए इसका इस्तेमाल करते हैं.
- रनफ़ाइल ट्री में कई तरह की लेगसी एंट्री मौजूद होती हैं. इन्हें भी दिखाना ज़रूरी होता है.
रनफ़ाइल, RunfilesProvider
का इस्तेमाल करके इकट्ठा की जाती हैं. इस क्लास का एक इंस्टेंस, कॉन्फ़िगर की गई टारगेट (जैसे, लाइब्रेरी) और ट्रांज़िट समय में बंद होने की ज़रूरतों को दिखाता है. साथ ही, इन्हें नेस्ट किए गए सेट की तरह इकट्ठा किया जाता है (असल में, यह कवर के नीचे नेस्ट किए गए सेट का इस्तेमाल करके लागू किया जाता है): हर टारगेट अपनी डिपेंडेंसी के रनटाइम फ़ाइल को खुद बनाता है, फिर कुछ सेट को डिपेंडेंसी ग्राफ़ में भेजता है. RunfilesProvider
इंस्टेंस में दो Runfiles
स्टेटेंस होते हैं, एक तब, जब नियम "डेटा" एट्रिब्यूट पर निर्भर होता है और
हर अन्य तरह की आने वाली डिपेंडेंसी के लिए. ऐसा इसलिए होता है, क्योंकि टारगेट कभी-कभी डेटा एट्रिब्यूट के ज़रिए अलग होने पर अलग रनफ़ाइल दिखाता है. यह एक पुरानी पहल है, जिसे हम अब तक पूरा नहीं कर पाए हैं.
बाइनरी की रनफ़ाइल को RunfilesSupport
के इंस्टेंस के तौर पर दिखाया गया है. यह
Runfiles
से अलग है, क्योंकि RunfilesSupport
के लिए असल में बनाया जा सकता है
जबकि Runfiles
सिर्फ़ मैपिंग है. इसके लिए, यहां दिए गए अतिरिक्त कॉम्पोनेंट की ज़रूरत होती है:
- इनपुट रनफ़ाइल मेनिफ़ेस्ट. यह रनफ़ाइल ट्री का क्रम से बताया गया ब्यौरा है. इसका इस्तेमाल रनफ़ाइल ट्री के कॉन्टेंट के लिए प्रॉक्सी के तौर पर किया जाता है. वहीं, बेज़ल यह मानकर चलता है कि रनफ़ाइल का ट्री सिर्फ़ तब बदलता है, जब मेनिफ़ेस्ट का कॉन्टेंट बदलता है.
- आउटपुट रनफ़ाइल मेनिफ़ेस्ट. इसका इस्तेमाल रनटाइम लाइब्रेरी में किया जाता है, जो रैंडफ़ाइल ट्री को मैनेज करती है. खास तौर पर, Windows पर. हालांकि, कई बार ये लिंक सिंबॉलिक लिंक के साथ काम नहीं करते.
- रनबोर्ड वाली फ़ाइल. किसी रनफ़ाइल ट्री के मौजूद होने के लिए, ज़रूरी है कि आप एक सिमलिंक ट्री बनाएं और सिंबॉलिक आर्टफ़ैक्ट पर ले जाएं. डिपेंडेंसी किनारों की संख्या कम करने के लिए, इन फ़ाइलों को दिखाने के लिए, रनफ़ाइल में मौजूद मिडलमैन का इस्तेमाल किया जा सकता है.
- उस बाइनरी को चलाने के लिए कमांड लाइन तर्क जिसकी रनफ़ाइल,
RunfilesSupport
ऑब्जेक्ट दिखाता है.
पहलू
आसपेक्ट, "डिपेंडेंसी ग्राफ़ को कंप्यूट करने की सुविधा का इस्तेमाल करके आगे बढ़ने" का तरीका है. बैज़ल के उपयोगकर्ताओं के लिए
यहां
बताया गया है. proto_library
प्रेरणा देने वाला एक अच्छा उदाहरण है: प्रोटोकॉल बफ़रproto_library
कॉन्फ़िगर किए गए टारगेट की तरह ही, वे स्काईफ़्रेम में SkyValue
के तौर पर दिखाए जाते हैं और उन्हें बनाने का तरीका काफ़ी हद तक कॉन्फ़िगर किए गए टारगेट की तरह ही होता है: उनके पास ConfiguredAspectFactory
नाम का एक फ़ैक्ट्री क्लास होता है, जिसका ऐक्सेस RuleContext
होता है. हालांकि, यह कॉन्फ़िगर किए गए टारगेट फ़ैक्ट्री के उलट, यह SkyValue
और इसके अटैच करने वालों को अटैच करता है.
डिपेंडेंसी ग्राफ़ का इस्तेमाल करके बनाए गए पहलुओं का सेट, Attribute.Builder.aspects()
फ़ंक्शन का इस्तेमाल करके, हर एट्रिब्यूट के लिए तय किया जाता है. इस प्रक्रिया में हिस्सा लेने वाली कुछ नाम वाली
कक्षा हैं:
AspectClass
में अलग-अलग चीज़ों की जानकारी को लागू किया गया है. यह या तो Java में है (इस मामले में यह एक सब-क्लास है) या Starlark में (इस मामले में, यह एकStarlarkAspectClass
का इंस्टेंस है). यहRuleConfiguredTargetFactory
का जैसा है.AspectDefinition
, आसपेक्ट की परिभाषा है. इसमें, सेवा देने वाली ज़रूरी कंपनियां शामिल होती हैं. साथ ही, इसमें सेवा देने वाली कंपनियां भी शामिल होती हैं और वे अपने लागू करने के बारे में जानकारी देती हैं, जैसे कि सहीAspectClass
इंस्टेंस. यहRuleClass
जैसा है.AspectParameters
, किसी ऐसे पहलू को पैरामीटर देने का तरीका है जो डिपेंडेंसी ग्राफ़ में लागू होता है. फ़िलहाल, यह स्ट्रिंग को मैप करने के लिए एक स्ट्रिंग है. प्रोटोकॉल बफ़र, मददगार क्यों है, इसका एक अच्छा उदाहरण: अगर किसी भाषा में कई एपीआई हैं, तो वह जानकारी जिस एपीआई के लिए प्रोटोकॉल बफ़र बनाए जाने चाहिए उसे डिपेंडेंसी ग्राफ़ में प्रसारित किया जाना चाहिए.Aspect
में उस डेटा का प्रतिनिधित्व किया जाता है जिसकी ज़रूरत उस डिपेंडेंसी को कैलकुलेट करने के लिए होती है जो डिपेंडेंसी ग्राफ़ को हटा देता है. इसमें आसपेक्ट क्लास, उसकी परिभाषा, और उसके पैरामीटर शामिल होते हैं.RuleAspect
एक ऐसा फ़ंक्शन है जिससे यह तय किया जाता है कि किसी खास नियम को कौनसा दायरा लागू करना चाहिए. यह एकRule
->Aspect
फ़ंक्शन है.
हालांकि, इसमें कुछ दिक्कतें हैं, जो कुछ दूसरे पहलुओं से भी जुड़ी हो सकती हैं. उदाहरण के लिए, जावा आईडीई के लिए क्लासपाथ इकट्ठा करने वाला कोई पहलू, जो क्लासपाथ पर मौजूद सभी . Jar फ़ाइलों के बारे में जानना चाहेगा, लेकिन उनमें से कुछ प्रोटोकॉल बफ़र होते हैं. इस मामले में, IDE आसपेक्ट रेशियो (proto_library
नियम + Java प्रोटो आसपेक्ट) पेयर में जोड़ना होगा.
आसपेक्ट रेशियो की जटिलता को क्लास AspectCollection
में कैप्चर किया गया है.
प्लैटफ़ॉर्म और टूलचेन
Bazel, एक से ज़्यादा प्लैटफ़ॉर्म बिल्ड के साथ काम करता है. इसका मतलब है कि यहां ऐसे कई आर्किटेक्चर हो सकते हैं जहां बिल्ड ऐक्शन होते हैं और कई आर्किटेक्चर मौजूद होते हैं, जिनके लिए कोड बनाया जाता है. इन आर्किटेक्चर को बेज़ल पार्लर में प्लैटफ़ॉर्म कहा जाता है. पूरा दस्तावेज़ यहां देखें
प्लैटफ़ॉर्म के बारे में कंस्ट्रेंट सेटिंग (जैसे कि "सीपीयू आर्किटेक्चर" का कॉन्सेप्ट) से कंस्ट्रेंट वैल्यू (जैसे कि x86_64) जैसे खास सीपीयू से की-वैल्यू मैपिंग से तय होता है. हमारे पास @platforms
डेटा स्टोर करने की जगह में, सबसे ज़्यादा इस्तेमाल होने वाली कंस्ट्रेंट सेटिंग और वैल्यू का "डिक्शनरी" है.
टूलचेन का सिद्धांत इस बात पर निर्भर करता है कि बिल्ड किन प्लैटफ़ॉर्म पर चल रहा है और किन प्लैटफ़ॉर्म को टारगेट किया जा रहा है, इसके लिए अलग-अलग कंपाइलर का इस्तेमाल करना पड़ सकता है. उदाहरण के लिए, खास C++ टूलचेन एक खास ओएस पर चल सकता है और कुछ दूसरे ओएस को टारगेट कर सकता है. बेज़ल को C++ कंपाइलर तय करना होता है, जिसका इस्तेमाल सेट एक्ज़ीक्यूशन और टारगेट प्लैटफ़ॉर्म के आधार पर किया जाता है (टूलचेन के लिए दस्तावेज़ यहां देखें).
ऐसा करने के लिए, टूलटिप को लागू करने के सेट और टारगेट प्लैटफ़ॉर्म कंस्ट्रेंट के साथ एनोटेट किया जाता है. ऐसा करने के लिए, टूलचेन की परिभाषा दो भागों में बांटी जाती है:
toolchain()
नियम, जो एक्ज़ीक्यूशन और टारगेट के सेट के बारे में बताता है और एक टूलचेन का समर्थन करता है और बताता है कि टूलचेन का किस तरह का (जैसे कि C++ या Java) है और बाद में इसेtoolchain_type()
नियम से दिखाया जाता है- भाषा के हिसाब से एक नियम, जो असल टूलचेन की जानकारी देता है (जैसे कि
cc_toolchain()
)
ऐसा इसलिए किया गया है, क्योंकि टूल-चेन रिज़ॉल्यूशन और भाषा के हिसाब से बने *_toolchain()
नियमों के लिए, हमें हर टूलचेन की कंस्ट्रेंट के बारे में ज़्यादा जानकारी होनी चाहिए. इसलिए, उन्हें लोड होने में ज़्यादा समय लगता है.
एक्ज़ीक्यूशन प्लैटफ़ॉर्म को इनमें से किसी एक तरीके से शामिल किया जाता है:
register_execution_platforms()
फ़ंक्शन का इस्तेमाल करके, Workspace की फ़ाइल में- --extra_execution_platforms कमांड लाइन विकल्प का इस्तेमाल करके कमांड लाइन पर
एक्ज़ीक्यूशन के लिए उपलब्ध प्लैटफ़ॉर्म के सेट का हिसाब
RegisteredExecutionPlatformsFunction
लगाया जाता है .
कॉन्फ़िगर किए गए टारगेट का टारगेट प्लैटफ़ॉर्म, PlatformOptions.computeTargetPlatform()
से तय होता है . इस सूची में प्लैटफ़ॉर्म शामिल हैं, क्योंकि आखिरकार हम टारगेट किए जाने वाले कई प्लैटफ़ॉर्म पर काम करना चाहते हैं. हालांकि, फ़िलहाल यह लागू नहीं किया गया है.
कॉन्फ़िगर किए गए टारगेट के लिए इस्तेमाल किए जाने वाले टूलटिप का सेट
ToolchainResolutionFunction
तय करता है. यह एक फ़ंक्शन है:
- रजिस्टर किए गए टूलचेन का सेट (Workspace फ़ाइल और कॉन्फ़िगरेशन)
- एक्ज़ीक्यूशन और टारगेट प्लैटफ़ॉर्म (कॉन्फ़िगरेशन में)
- कॉन्फ़िगर किए गए टारगेट के लिए ज़रूरी टूलचेन टाइप का सेट
UnloadedToolchainContextKey)
- कॉन्फ़िगर किए गए टारगेट (
exec_compatible_with
एट्रिब्यूट) और कॉन्फ़िगरेशन (--experimental_add_exec_constraints_to_targets
),UnloadedToolchainContextKey
में, एक्ज़ीक्यूशन प्लैटफ़ॉर्म की कंस्ट्रेंट का सेट
इसका नतीजा UnloadedToolchainContext
है, जो असल में टूलचेन टाइप से चुना गया मैप है. इसे ToolchainTypeInfo
इंस्टेंस के तौर पर दिखाया गया है. यह चुने गए टूलचेन के लेबल से दिखता है. इसे "अनलोड किया गया" कहा जाता है, क्योंकि इसमें
टूलचेन नहीं होते, सिर्फ़ उनके लेबल होते हैं.
इसके बाद, टूलचेन ResolvedToolchainContext.load()
के इस्तेमाल से लोड किए जाते हैं और कॉन्फ़िगर किए गए उस टारगेट को लागू करके इस्तेमाल किए जाते हैं जिसके लिए उन्हें अनुरोध किया गया.
हमारे पास एक लेगसी सिस्टम भी है, जो इस बात पर निर्भर करता है कि "होस्ट" का एक ही कॉन्फ़िगरेशन और टारगेट कॉन्फ़िगरेशन, कॉन्फ़िगरेशन के अलग-अलग फ़्लैग के ज़रिए किए जा रहे हैं या नहीं. जैसे, --cpu
. हम धीरे-धीरे ऊपर दिए गए सिस्टम में बदल रहे हैं. ऐसे मामलों से निपटने के लिए जहां लोग लेगसी कॉन्फ़िगरेशन की वैल्यू पर भरोसा करते हैं, हमने पुराने फ़्लैग और नए स्टाइल वाले प्लैटफ़ॉर्म के बीच अनुवाद के लिए प्लैटफ़ॉर्म मैपिंग लागू की है.
उनका कोड PlatformMappingFunction
में है और बिना स्टार वाली "छोटी भाषा" का इस्तेमाल करता है.
कंस्ट्रेंट
कभी-कभी कोई व्यक्ति किसी टारगेट को सिर्फ़ कुछ प्लैटफ़ॉर्म के साथ काम करना चाहता है. इस तरह की उपलब्धि हासिल करने के लिए, बेज़ल कई तरीकों से मदद करता है:
- नियम से जुड़ी खास पाबंदियां
environment_group()
/environment()
- प्लैटफ़ॉर्म कंस्ट्रेंट
ज़्यादातर नियम- स्टोरेज के लिए कंस्ट्रेंट का इस्तेमाल, Google में Java के नियमों के लिए किया जाता है. हालांकि, वे बाहर होते हैं, लेकिन बेज़ल में उपलब्ध नहीं होते, लेकिन सोर्स कोड में उसका रेफ़रंस हो सकता है. इस एट्रिब्यूट को कंट्रोल करने वाले एट्रिब्यूट को
constraints=
कहा जाता है .
पर्यावरण_ग्रुप() और एनवायरमेंट()
ये नियम लंबे समय से चले आ रहे हैं और इनका ज़्यादा इस्तेमाल नहीं किया जाता.
बिल्ड के सभी नियम यह एलान कर सकते हैं कि उन्हें "परिवेश" के लिए कहां बनाया जा सकता है, जहां "परिवेश" environment()
नियम का इंस्टेंस है.
किसी नियम के लिए इस्तेमाल किए जा सकने वाले एनवायरमेंट तय करने के कई तरीके हैं:
restricted_to=
एट्रिब्यूट से. यह स्पेसिफ़िकेशन का सबसे सीधा तरीका है. यह इस ग्रुप के लिए लागू होने वाले नियमों के बारे में बताता है.compatible_with=
एट्रिब्यूट से. यह बताता है कि डिफ़ॉल्ट रूप से "स्टैंडर्ड" एनवायरमेंट के साथ-साथ, नियम की सुविधा क्या है.- पैकेज-लेवल के एट्रिब्यूट
default_restricted_to=
औरdefault_compatible_with=
से. environment_group()
के नियमों में डिफ़ॉल्ट स्पेसिफ़िकेशन के ज़रिए. हर एनवायरमेंट थीम के हिसाब से मिलते-जुलते ऐप्लिकेशन के ग्रुप से जुड़ा होता है, जैसे कि "सीपीयू आर्किटेक्चर", "JDK वर्शन" या "मोबाइल ऑपरेटिंग सिस्टम". एनवायरमेंट ग्रुप की परिभाषा में यह भी शामिल होता है कि अगरrestricted_to=
/environment()
एट्रिब्यूट में कुछ और न बताया गया हो, तो किन एनवायरमेंट में काम करने की सुविधा होनी चाहिए. ऐसा नियम जिसमें सभी एट्रिब्यूट शामिल नहीं होते, वे डिफ़ॉल्ट रूप से लागू हो जाते हैं.- नियम की क्लास के डिफ़ॉल्ट विकल्प के ज़रिए. यह दिए गए नियम की क्लास के सभी इंस्टेंस के लिए, ग्लोबल डिफ़ॉल्ट सेटिंग को बदल देता है. उदाहरण के लिए, इसका इस्तेमाल करके सभी
*_test
नियमों को टेस्ट किया जा सकता है. इसके लिए, हर इंस्टेंस में साफ़ तौर पर यह निर्देश देना ज़रूरी नहीं है.
environment()
को एक सामान्य नियम के तौर पर लागू किया जाता है, जबकि environment_group()
, Target
की सब-क्लास है, न कि Rule
(EnvironmentGroup
) और
यह एक ऐसा फ़ंक्शन है जो Starlark
(StarlarkLibrary.environmentGroup()
) से डिफ़ॉल्ट रूप से उपलब्ध होता है और
इससे एक ही टारगेट बन जाता है. यह ऐसे साइक्लिकल डिपेंडेंसी से बचने के लिए है जो जनरेट होता है, क्योंकि हर एनवायरमेंट को उस एनवायरमेंट ग्रुप के बारे में बताना होता है जिससे वह जुड़ा है. साथ ही, हर एनवायरमेंट ग्रुप को अपने डिफ़ॉल्ट एनवायरमेंट का एलान करना होता है.
--target_environment
कमांड लाइन विकल्प का इस्तेमाल करके, किसी खास इलाके में ऐप्लिकेशन बनाने पर पाबंदी लगाई जा सकती है.
कंस्ट्रेंट की जांच को RuleContextConstraintSemantics
और TopLevelConstraintSemantics
में लागू किया जाता है.
प्लैटफ़ॉर्म कंस्ट्रेंट
टारगेट और साथ काम करने वाले प्लैटफ़ॉर्म के बारे में बताने के लिए, मौजूदा "आधिकारिक" तरीके का इस्तेमाल किया जाता है. इसके लिए, उन ही प्रतिबंधों का इस्तेमाल किया जाता है जिनका इस्तेमाल टूलचेन और प्लैटफ़ॉर्म के बारे में बताने के लिए किया जाता है. पुल के अनुरोध #10945 में इसकी समीक्षा की जा रही है.
किसे दिखे
अगर आप एक बड़े कोडबेस पर बहुत सारे डेवलपर (जैसे, Google) के साथ काम करते हैं, तो आप इस बात का ध्यान रखें कि दूसरे लोग किसी भी तरह के कोड को अपने कोड के आधार पर इस्तेमाल न करें. ऐसा न होने पर, Hyrum के कानून के मुताबिक, लोगों को उन चीज़ों पर भरोसा करना होगा जिन्हें आपने लागू करने के बारे में जानकारी दी थी.
बैज़ल, किसको दिखे नाम के सिस्टम की मदद से काम करता है: एलान किया जा सकता है कि किसी खास टारगेट को सिर्फ़, किसको दिखे एट्रिब्यूट का इस्तेमाल करके किया जा सकता है. हालांकि, यह एट्रिब्यूट थोड़ा खास है, क्योंकि इसमें लेबल की सूची मौजूद होती है. हालांकि, ये लेबल किसी खास टारगेट के लिए पॉइंटर के बजाय, पैकेज के नाम पर पैटर्न को कोड में बदल सकते हैं. (हां, यह डिज़ाइन में कोई गड़बड़ी है.)
इसे नीचे दी गई जगहों पर लागू किया जाता है:
RuleVisibility
इंटरफ़ेस, विज़िबिलिटी डिक्लेरेशन के बारे में बताता है. यह या तो कॉन्सटेंट (पूरी तरह से सार्वजनिक या पूरी तरह से निजी) या लेबल की सूची हो सकती है.- लेबल, पैकेज (पहले से तय पैकेज की सूची) या पैकेज (सीधे)
//pkg:__pkg__
या पैकेज (//pkg:__subpackages__
) के सब-ट्री का रेफ़रंस दे सकते हैं. यह कमांड लाइन सिंटैक्स से अलग होता है, जो//pkg:*
या//pkg/...
का इस्तेमाल करता है. - पैकेज ग्रुप को उनके टारगेट (
PackageGroup
) और कॉन्फ़िगर किए गए टारगेट (PackageGroupConfiguredTarget
) के तौर पर लागू किया जाता है. अगर हम चाहते हैं, तो हम इन्हें आसान नियमों से बदल सकते हैं. उनका लॉजिक,PackageSpecification
की मदद से लागू किया जाता है: यह//pkg/...
जैसेPackageGroupContents
के पैटर्न से मेल खाता है, जोpackage_group
केpackages
एट्रिब्यूट से मेल खाता है. साथ ही,PackageSpecificationProvider
को ट्रांज़िशन के लिएincludes
के साथ एग्रीगेट किया जाता है.package_group
- विज़िबिलिटी लेबल की सूचियों से डिपेंडेंसी में कन्वर्ज़न,
DependencyResolver.visitTargetVisibility
और कुछ दूसरी जगहों पर किया जाता है. - असल जांच
CommonPrerequisiteValidator.validateDirectPrerequisiteVisibility()
में की जाती है
नेस्टेड सेट
कभी-कभी, कॉन्फ़िगर किया गया टारगेट अपनी डिपेंडेंसी से फ़ाइलों के एक सेट को इकट्ठा करता है, अपना डेटा जोड़ता है, और इकट्ठा किए गए सेट को ट्रांज़िटिव जानकारी देने वाले प्रोवाइडर में रैप करता है, ताकि कॉन्फ़िगर किए गए टारगेट ठीक उसी तरह काम कर सकें. उदाहरण:
- बिल्ड के लिए इस्तेमाल की गई C++ हेडर फ़ाइलें
- ऑब्जेक्ट फ़ाइलें, जो
cc_library
की ट्रांज़िटिव क्लोज़िंग को दिखाती हैं - Java नियम के कंपाइल करने या चलाने के लिए, . Jar फ़ाइलों का सेट जो क्लास पाथ पर होनी चाहिए
- Python नियम के ट्रांज़िट समय बंद होने पर Python फ़ाइलों का सेट
उदाहरण के लिए, List
या Set
का इस्तेमाल करके, यह नया तरीका अपनाकर, हम क्वॉड्रेटिक मेमोरी का इस्तेमाल करेंगे: अगर N नियमों की एक चेन है और हर नियम एक फ़ाइल जोड़ता है, तो हमारे पास कलेक्शन के 1+2+...+N सदस्य होते हैं.
इस समस्या को हल करने के लिए, हमने एक NestedSet
का कॉन्सेप्ट तैयार किया. यह एक डेटा स्ट्रक्चर है जो अन्य NestedSet
इंस्टेंस और अपने खुद के कुछ सदस्यों से मिलकर बना है. इसलिए, यह एक सेट वाला असाइकलिक ग्राफ़ बनाता है. इनमें कोई बदलाव नहीं होता. साथ ही, इनमें सदस्यों को बदला जा सकता है. हम कई इटरेशन ऑर्डर तय करते हैं (NestedSet.Order
): पहले से ऑर्डर करना, पोस्टऑर्डर, और टोपोग्राफ़िकल (कोई नोड हमेशा अपने पूर्वजों के बाद आता है) और "इससे फ़र्क़ नहीं पड़ता".
Starlark में डेटा स्ट्रक्चर का नाम depset
है.
आर्टफ़ैक्ट और कार्रवाइयां
असल बिल्ड में कई निर्देश होते हैं, जिन्हें उपयोगकर्ता की पसंद के आउटपुट
बनाने के लिए चलाना ज़रूरी होता है. निर्देशों को क्लास Action
के इंस्टेंस के तौर पर दिखाया जाता है और फ़ाइलों को क्लास Artifact
के इंस्टेंस के तौर पर दिखाया जाता है. इन्हें दो हिस्सों में बांटा गया है. ये दो हिस्सों में बंटे होते हैं. इन्हें "साइकल ग्राफ़" कहते हैं.
आर्टफ़ैक्ट दो तरह के होते हैं: सोर्स आर्टफ़ैक्ट (जो बेज़ेल एक्ज़ीक्यूट करने से पहले उपलब्ध होते हैं) और जिन्हें आर्टफ़ैक्ट इस्तेमाल करने की ज़रूरत होती है. हासिल किए गए आर्टफ़ैक्ट के अलग-अलग टाइप हो सकते हैं:
- **सामान्य आर्टफ़ैक्ट. **अप-टू-डेट डेटा देखने के लिए, चेकसम का इस्तेमाल करके जांच की जाती है, जिसमें mtime को शॉर्टकट के तौर पर इस्तेमाल किया जाता है. हालांकि, अगर फ़ाइल का समय नहीं बदला है, तो हम फ़ाइल को नहीं देखते हैं.
- सिंपलिंक आर्टफ़ैक्ट की ऐसी समस्याएं जो ठीक नहीं हैं. रीडलिंक() को कॉल करके, इन्हें अप-टू-डेट रखा जाता है. सामान्य आर्टफ़ैक्ट के उलट, ये लटकते सिमलिंक हो सकते हैं. आम तौर पर, इसका इस्तेमाल तब किया जाता है, जब कोई व्यक्ति कुछ फ़ाइलों को किसी क्रम में संग्रहित करता है.
- ट्री आर्ट. ये एक फ़ाइल नहीं हैं, लेकिन डायरेक्ट्री ट्री हैं. अप-टू-डेट डेटा देखने के लिए, फ़ाइलों के सेट और उनके कॉन्टेंट की जांच की जाती है. उन्हें
TreeArtifact
के तौर पर दिखाया जाता है. - मेटाडेटा का एक जैसा आर्टफ़ैक्ट. इन आर्टफ़ैक्ट में किए गए बदलावों का इस्तेमाल करके, प्रोजेक्ट दोबारा नहीं बनता. इसका इस्तेमाल खास तौर पर, स्टैंप से जुड़ी जानकारी तैयार करने के लिए किया जाता है. हम सिर्फ़ मौजूदा समय में बदलाव होने की वजह से, इसे फिर से तैयार नहीं करना चाहते.
कोई ठोस वजह नहीं है कि स्रोत आर्टफ़ैक्ट ट्री ट्री आर्टफ़ैक्ट नहीं हो सकते या सिमलिंक आर्टफ़ैक्ट का समाधान नहीं किया जा सकता है. यह बस इतना ही है कि हमने इसे अभी तक लागू नहीं किया है. हालांकि, हमें BUILD
फ़ाइल में सोर्स डायरेक्ट्री का रेफ़रंस देना, बेज़ेल में लंबे समय से चली आ रही गलतफ़हमी की समस्याओं में से एक है. हमारे पास कई तरह के काम हैं जिनमें BAZEL_TRACK_SOURCE_DIRECTORIES=1
जेवीएम प्रॉपर्टी चालू होती है
एक खास तरह के Artifact
बिचौलियां हैं. इन्हें Artifact
तरीकों से दिखाया जाता है, जो MiddlemanAction
के आउटपुट हैं. इनका इस्तेमाल कुछ चीज़ों को खास तौर पर करने के लिए किया जाता है:
- एग्रीगेट किए गए मध्यस्थों का इस्तेमाल, आर्टफ़ैक्ट को एक साथ ग्रुप करने के लिए किया जाता है. इसका मतलब यह है कि अगर बहुत सारी कार्रवाइयां इनपुट के एक ही बड़े सेट का इस्तेमाल करती हैं, तो हमारे पास N*M डिपेंडेंसी वाले किनारे नहीं हैं, सिर्फ़ N+M हैं (इन्हें नेस्ट किए गए सेट से बदला जा रहा है)
- शेड्यूलिंग डिपेंडेंसी मिडलमैन यह पक्का करते हैं कि कोई कार्रवाई दूसरी से पहले चले.
इसका इस्तेमाल ज़्यादातर, लिंट करने के साथ-साथ C++ कंपाइलेशन के लिए भी किया जाता है (ज़्यादा जानकारी के लिए
CcCompilationContext.createMiddleman()
देखें) - रनफ़ाइल ट्रीमैन को रनफ़ाइल की मौजूदगी को पक्का करने के लिए इस्तेमाल किया जाता है. ऐसा इसलिए किया जाता है, ताकि रनटाइम के लिए जनरेट किए गए आउटपुट मेनिफ़ेस्ट और हर एक आर्टफ़ैक्ट पर निर्भर होने की ज़रूरत न पड़े.
कार्रवाइयों को सबसे सही तरीके से निर्देश के तौर पर समझा जाता है. इन्हें चलाने की ज़रूरत होती है, एनवायरमेंट की ज़रूरत होती है, और आउटपुट के सेट की ज़रूरत होती है. नीचे दी गई चीज़ें कार्रवाई के ब्यौरे के मुख्य घटक हैं:
- वह कमांड लाइन जिसे चलाना है
- इनपुट से जुड़े इन आर्टफ़ैक्ट की ज़रूरत होती है
- ऐसे एनवायरमेंट वैरिएबल जिन्हें सेट करना ज़रूरी है
- एनवायरमेंट (जैसे कि प्लैटफ़ॉर्म) के बारे में बताने वाली ऐसी व्याख्याएं जिन्हें चलाने की ज़रूरत होती है \
इसके कुछ और विशेष मामले भी हैं, जैसे कि ऐसी फ़ाइल लिखना जिसका कॉन्टेंट
Ba इतने मशहूर है. वे AbstractAction
की सब-क्लास हैं. ज़्यादातर कार्रवाइयां
एक SpawnAction
या StarlarkAction
होती हैं (एक ही तरह की, वे यकीनन अलग-अलग
क्लास नहीं होनी चाहिए), हालांकि Java और C++ की अपनी टाइप की कार्रवाइयां
(JavaCompileAction
, CppCompileAction
और CppLinkAction
) होती हैं.
आखिर में, हम सब कुछ SpawnAction
में ट्रांसफ़र करना चाहते हैं. JavaCompileAction
काफ़ी पास है, लेकिन .d फ़ाइल को पार्स करने और स्कैन करने की वजह से C++ एक खास मामला है.
ऐक्शन ग्राफ़ को ज़्यादातर स्काईफ़्रेम ग्राफ़ में "एम्बेड किया गया" माना जाता है: सैद्धांतिक तौर पर, किसी कार्रवाई के चलने को ActionExecutionFunction
के नाम के तौर पर दिखाया जाता है. स्काईफ़्रेम डिपेंडेंसी एज से मैप करने के लिए, ऐक्शन ग्राफ़ डिपेंडेंसी एज को ActionExecutionFunction.getInputDeps()
और Artifact.key()
में बताया गया है. स्काईफ़्रेम किनारों की संख्या कम रखने के लिए, कुछ ऑप्टिमाइज़ेशन में शामिल किया गया है:
- हासिल किए गए आर्टफ़ैक्ट के खुद के
SkyValue
नहीं होते. इसके बजाय, इसे जनरेट करने वाली कार्रवाई की कुंजी का पता लगाने के लिए,Artifact.getGeneratingActionKey()
का इस्तेमाल किया जाता है - नेस्ट किए गए सेट की अपनी स्काईफ़्रेम कुंजी होती है.
शेयर की गई कार्रवाइयां
कुछ कार्रवाइयां, कॉन्फ़िगर किए गए कई टारगेट से जनरेट की जाती हैं. हालांकि, Starlark के नियम सीमित होते हैं, क्योंकि उन्हें सिर्फ़ अपनी कार्रवाइयों को उनके कॉन्फ़िगरेशन और उनके पैकेज से तय की गई डायरेक्ट्री में डालने की अनुमति होती है. हालांकि, एक ही पैकेज में दिए गए नियम एक-दूसरे से अलग हो सकते हैं. हालांकि, Java में लागू किए गए नियम, तय की गई जगहों पर कहीं भी लागू हो सकते हैं.
इसे गलत सुविधा माना जाता है. इसे हटाना बहुत मुश्किल होता है, क्योंकि इसकी वजह से एक्ज़ीक्यूशन समय में काफ़ी बचत होती है. उदाहरण के लिए, किसी सोर्स फ़ाइल को किसी तरह प्रोसेस करने की ज़रूरत होती है और उस फ़ाइल का रेफ़रंस एक साथ कई नियमों (हैंडवे-हैंडवे) से मिलता है. कुछ रैम की कीमत में ऐसा होता है: शेयर की गई कार्रवाई का हर इंस्टेंस, मेमोरी में अलग से सेव किया जाना चाहिए.
अगर दो कार्रवाइयों से एक ही आउटपुट फ़ाइल जनरेट होती है, तो उनका बिलकुल एक जैसा होना ज़रूरी है:
एक ही इनपुट होना, एक ही आउटपुट होना, और एक ही कमांड लाइन चलाना. Actions.canBeShared()
समानता संबंध को लागू किया जाता है और हर कार्रवाई को देखकर, विश्लेषण और लागू करने के चरणों के बीच पुष्टि की जाती है.
इसे SkyframeActionExecutor.findAndStoreArtifactConflicts()
में लागू किया जाता है. यह
बेज़ल की कुछ जगहों में से एक है जिसके लिए बिल्ड के "ग्लोबल" व्यू की ज़रूरत होती है.
स्क्रिप्ट रन करने का तरीका
यह वही समय होता है जब बेज़ल असल में बिल्ड ऐक्शन चलाना शुरू करता है, जैसे कि आउटपुट करने वाले निर्देश.
विश्लेषण के चरण के बाद, बेज़ल सबसे पहले यह तय करते हैं कि कौनसे आर्टफ़ैक्ट बनाए जाने चाहिए. इसका तर्क TopLevelArtifactHelper
में एन्कोड किया गया है. साफ़ तौर पर, यह कमांड लाइन पर कॉन्फ़िगर किए गए टारगेट का filesToBuild
है. साथ ही, यह साफ़ तौर पर ज़ाहिर करने के मकसद से खास आउटपुट ग्रुप के कॉन्टेंट का हिस्सा है (अगर यह टारगेट कमांड लाइन पर है, तो "ये आर्टफ़ैक्ट बनाएं".
अगला कदम एक्ज़ीक्यूशन रूट बनाना है. बेज़ल के पास फ़ाइल सिस्टम में अलग-अलग जगहों से सोर्स पैकेज (--package_path
) पढ़ने का विकल्प होता है. इसलिए, इसे फ़ुल सोर्स ट्री में स्थानीय तौर पर लागू की जाने वाली कार्रवाइयां देनी होती हैं. इसे SymlinkForest
क्लास मैनेज करती है. यह विश्लेषण के फ़ेज़ में इस्तेमाल किए गए हर टारगेट को नोट करती है और एक डायरेक्ट्री ट्री बनाती है. इस ट्री में, हर पैकेज के लिए, असल जगह पर इस्तेमाल किए गए टारगेट को सिम्युलेट किया जाता है. दूसरा विकल्प यह होगा कि निर्देशों के लिए सही पाथ पास किए जाएं (--package_path
खाते में).
यह तरीका काम नहीं करता, क्योंकि:
- जब पैकेज पैकेज से किसी दूसरे पाथ पर ले जाया जाता है, तो कार्रवाई कमांड लाइन बदल जाती है (यह आम तौर पर होती है)
- अगर कोई कार्रवाई दूर से चलाई जाती है, तो यह कमांड कमांड लाइन में अलग होती है.
- इसके लिए, इस्तेमाल किए जा रहे टूल के लिए खास कमांड लाइन ट्रांसफ़ॉर्मेशन की ज़रूरत होती है (Java क्लासपाथ और C++ में पाथ शामिल करने के बीच के अंतर पर विचार करें)
- किसी कार्रवाई की कमांड लाइन बदलने से उसकी कार्रवाई कैश एंट्री अमान्य हो जाती है
--package_path
धीरे काम कर रहा है और लगातार काम नहीं कर रहा है
इसके बाद, बेज़ल कार्रवाई ग्राफ़ (दो हिस्सों में बंटे, डायरेक्ट किए गए ग्राफ़ में
कार्रवाइयां और उनके इनपुट और आउटपुट कलाकृतियों को मिलाकर) को चलाना और चलाना शुरू करती हैं.
हर एक्ज़ीक्यूशन को लागू करने का तरीका, SkyValue
क्लास ActionExecutionValue
के इंस्टेंस से पता चलता है.
किसी कार्रवाई को चलाना महंगा होता है. इसलिए, हमारे पास कैश मेमोरी की कुछ लेयर होती हैं, जिन्हें स्काईफ़्रेम के पीछे जाकर इस्तेमाल किया जा सकता है:
ActionExecutionFunction.stateMap
में डेटा मौजूद है, ताकि स्काईफ़्रेम कोActionExecutionFunction
कम खर्च में फिर से शुरू किया जा सके- लोकल ऐक्शन कैश में फ़ाइल सिस्टम की स्थिति के बारे में डेटा शामिल होता है
- रिमोट एक्ज़ीक्यूशन सिस्टम में आम तौर पर अपना कैश मेमोरी होती है
लोकल ऐक्शन की कैश मेमोरी
कैश मेमोरी एक और लेयर है, जो स्काईफ़्रेम के पीछे काम करती है. भले ही, कोई कार्रवाई स्काईफ़्रेम में बार-बार की जाए, लेकिन लोकल ऐक्शन कैश में उसे हिट किया जा सके. यह स्थानीय फ़ाइल सिस्टम की स्थिति को दिखाता है और इसे सीरीज़ के तौर पर डिस्क में रखा जाता है. इसका मतलब है कि जब कोई नया Bazel सर्वर चालू होगा, तो स्काईफ़्रेम ग्राफ़ खाली होने पर भी, लोकल ऐक्शन कैश मेमोरी को हिट मिल सकता है.
ActionCacheChecker.getTokenIfNeedToExecute()
वाले तरीके का इस्तेमाल करके हिट के लिए इस कैश को चेक किया जाता है .
इसके नाम के उलट, यह दिए गए आर्टफ़ैक्ट के पाथ से लेकर इसे बाहर करने वाली कार्रवाई तक का मैप होता है. इस कार्रवाई के बारे में बताया गया है:
- इसकी इनपुट और आउटपुट फ़ाइलों का सेट और उनका चेकसम
- इसकी "कार्रवाई कुंजी" आम तौर पर चलने वाली कमांड लाइन होती है. आम तौर पर, यह हर उस चीज़ को दिखाती है जिसे इनपुट फ़ाइलों के चेकसम (कैम) के ज़रिए कैप्चर नहीं किया गया है (जैसे कि
FileWriteAction
के लिए, यह लिखे गए डेटा का चेकसम है)
बेहद प्रयोग के तौर पर "टॉप-डाउन ऐक्शन कैश मेमोरी" भी उपलब्ध है, जो डेवलपमेंट की प्रक्रिया में है. इसका इस्तेमाल करके, कैश मेमोरी में कई बार जाने से बचा जा सकता है.
इनपुट खोजने और इनपुट को फ़िल्टर करने की सुविधा
इनपुट के सेट की तुलना में कुछ कार्रवाइयां ज़्यादा मुश्किल होती हैं. किसी कार्रवाई के इनपुट के सेट में दो तरह से बदलाव होते हैं:
- कार्रवाई करने से पहले, नए इनपुट ढूंढे जा सकते हैं. इसके अलावा, यह तय किया जा सकता है कि कुछ इनपुट
ज़रूरी नहीं हैं. कैननिकल उदाहरण C++ है, जहां इस बारे में सोच-समझकर अनुमान लगाना बेहतर होता है कि C++ फ़ाइल किस तरह से फ़ाइल को ट्रांज़िट समय में बंद करती है, ताकि हम हर फ़ाइल को रिमोट एक्ज़ीक्यूटर के तौर पर शेयर न करें, लेकिन हमारे पास हर हेडर फ़ाइल को "इनपुट" के तौर पर क्रम से लगाने का विकल्प है. इसकी वजह से, शीर्षक से जगह के आधार की तलाश करते ही, शीर्षक संख्या के आधार की तुलना करें.
#include
- किसी कार्रवाई से यह महसूस हो सकता है कि उसे प्रोसेस करते समय, कुछ फ़ाइलों का इस्तेमाल नहीं किया गया था. C++ को ".d फ़ाइलें" कहा जाता है: कंपाइलर बताता है कि इस तथ्य के बाद, हेडर की किन फ़ाइलों का इस्तेमाल किया गया है. इससे, Bazel इस समस्या का इस्तेमाल, चीज़ों को बेहतर तरीके से करने में होने वाली परेशानी से बच पाता है. यह शामिल स्कैनर की तुलना में बेहतर अनुमान देता है, क्योंकि यह कंपाइलर पर निर्भर करता है.
इन्हें 'कार्रवाई' पर दिए गए तरीकों का इस्तेमाल करके लागू किया जाता है:
Action.discoverInputs()
को कॉल किया गया. यह आर्टफ़ैक्ट का ऐसा नेस्ट किया गया सेट दिखाएगा जो ज़रूरी है. ये सोर्स आर्टफ़ैक्ट होने चाहिए, ताकि कार्रवाई ग्राफ़ में डिपेंडेंसी का कोई किनारा न हो. साथ ही, यह कॉन्फ़िगर किए गए टारगेट ग्राफ़ के बराबर न हो.- यह कार्रवाई,
Action.execute()
को कॉल करके की जाती है. Action.execute()
के आखिर में, कार्रवाई कोAction.updateInputs()
पर कॉल किया जा सकता है, ताकि बज़ेल को यह बताया जा सके कि इसके सभी इनपुट की ज़रूरत नहीं है. अगर इस्तेमाल किए गए इनपुट को 'इस्तेमाल नहीं किया गया' के तौर पर रिपोर्ट किया जाता है, तो इससे इंक्रीमेंटल बिल्ड बढ़ सकता है.
जब ऐक्शन कैश किसी नए ऐक्शन इंस्टेंस (जैसे कि सर्वर को रीस्टार्ट करने के बाद बनाया गया) पर कोई हिट दिखाता है, तो बेज़ल खुद ही updateInputs()
को कॉल करती है, ताकि इनपुट का सेट, इनपुट खोजने की सुविधा और नतीजे को फ़िल्टर करने के नतीजे को दिखा सके.
Starlark कार्रवाइयां कुछ इनपुट को इस्तेमाल न किए जाने के बारे में बताने के लिए, unused_inputs_list=
ctx.actions.run()
के तर्क का इस्तेमाल कर सकती हैं.
कार्रवाइयां करने के अलग-अलग तरीके: रणनीतियां/ActionContexts
कुछ कार्रवाइयां अलग-अलग तरीके से चलाई जा सकती हैं. उदाहरण के लिए, कमांड लाइन को स्थानीय तौर पर, अलग-अलग तरह से या अलग-अलग जगह से या एक-एक करके चलाया जा सकता है. इसे ऐसे सिद्धांत के तौर पर माना जाता है जिसमें ActionContext
या Strategy
का नाम शामिल हो.
किसी कार्रवाई के संदर्भ का लाइफ़ साइकल इस तरह से है:
- एक्ज़ीक्यूशन फ़ेज़ शुरू होने पर,
BlazeModule
इंस्टेंस से पूछा जाता है कि उनके पास कार्रवाई के कौनसे संदर्भ हैं. यहExecutionTool
के कंस्ट्रक्टर में होता है. कार्रवाई के कॉन्टेक्स्ट के टाइप की पहचान, JavaClass
इंस्टेंस के बारे में होती है. यह इंस्टेंसActionContext
के सब-इंटरफ़ेस के बारे में बताता है और कार्रवाई के कॉन्टेक्स्ट को लागू करना चाहिए. - उपलब्ध कार्रवाइयों में से सही कार्रवाई संदर्भ चुना जाता है
और उसे
ActionExecutionContext
औरBlazeExecutor
पर भेज दिया जाता है . - कार्रवाइयों से
ActionExecutionContext.getContext()
का इस्तेमाल करके संदर्भ का अनुरोध किया जाता है औरBlazeExecutor.getStrategy()
(इसे करने का सिर्फ़ एक तरीका होना चाहिए...)
रणनीतियां, अपना काम करने के लिए दूसरी रणनीतियों को मुफ़्त में इस्तेमाल करने की अनुमति देती हैं. उदाहरण के लिए, स्थानीय इन्वेंट्री की मदद से और तेज़ी से कार्रवाई शुरू करने वाली डाइनैमिक रणनीति में, इस रणनीति का इस्तेमाल किया जाता है.
ध्यान देने लायक एक रणनीति वह है जो स्थायी वर्कर प्रोसेस लागू करती है (WorkerSpawnStrategy
). आइडिया यह है कि कुछ टूल को शुरू करने में ज़्यादा समय लगता है. इसलिए, हर कार्रवाई के लिए एक नए टूल को शुरू करने के बजाय, इसे एक से ज़्यादा बार इस्तेमाल करना पड़ता है.
अगर टूल बदलता है, तो वर्कर प्रोसेस को रीस्टार्ट करना होगा. क्या किसी वर्कर को
फिर से इस्तेमाल किया जा सकता है, यह WorkerFilesHash
का इस्तेमाल करने वाले टूल के लिए चेकसम लेकर किया जाता है. यह टूल इस बात पर निर्भर करता है कि कार्रवाई के कौनसे इनपुट, टूल का हिस्सा हैं और इनपुट को कैसे दिखाते हैं. यह कार्रवाई, कार्रवाई का क्रिएटर Spawn.getToolFiles()
और Spawn
की रनफ़ाइल के ज़रिए तय की जाती है.
रणनीतियों (या कार्रवाई के संदर्भ!) के बारे में ज़्यादा जानकारी:
- अलग-अलग कार्रवाइयों से जुड़ी रणनीतियों के बारे में जानकारी यहां दी गई है.
- डाइनैमिक रणनीति के बारे में जानकारी यहां दी गई है. इसमें यह देखा जा सकता है कि किन कार्रवाइयों में स्थानीय और दूर, दोनों जगहों पर कार्रवाई की जाती है.
- स्थानीय स्तर पर कार्रवाइयां करने की प्रक्रिया की बारीकियों की जानकारी यहां उपलब्ध है.
स्थानीय संसाधन मैनेजर
Bazel, साथ-साथ कई कार्रवाइयां कर सकता है. स्थानीय कार्रवाइयों की संख्या को साथ-साथ कार्रवाई से अलग-अलग होना चाहिए: किसी कार्रवाई के लिए जितने ज़्यादा रिसॉर्स की ज़रूरत होगी, उतने ही कम इंस्टेंस चलने चाहिए, ताकि स्थानीय मशीन पर ज़्यादा दबाव न पड़े.
इसे ResourceManager
क्लास में लागू किया जाता है. हर कार्रवाई के बारे में, ऐसे लोकल रिसॉर्स के अनुमान में बताया जाना चाहिए जो ResourceSet
इंस्टेंस (सीपीयू और रैम) के तौर पर ज़रूरी है. इसके बाद, जब भी कार्रवाई से जुड़े संदर्भ में स्थानीय संसाधनों की ज़रूरत होती है, तो वे ResourceManager.acquireResources()
को कॉल करते हैं और ज़रूरी रिसॉर्स के उपलब्ध होने तक उन्हें ब्लॉक कर दिया जाता है.
लोकल रिसॉर्स मैनेजमेंट का ज़्यादा ब्यौरा यहां उपलब्ध है.
आउटपुट डायरेक्ट्री का स्ट्रक्चर
हर कार्रवाई के लिए आउटपुट डायरेक्ट्री में एक अलग जगह की ज़रूरत होती है, जहां वह आउटपुट देता है. आम तौर पर, मिलने वाली आर्टफ़ैक्ट की जगह की जानकारी इस तरह से दी जाती है:
$EXECROOT/bazel-out/<configuration>/bin/<package>/<artifact name>
किसी खास कॉन्फ़िगरेशन से जुड़ी डायरेक्ट्री का नाम कैसे तय किया जाता है? ऐसी दो प्रॉपर्टी हैं जो आपस में मेल खाती हैं:
- अगर एक ही बिल्ड में दो कॉन्फ़िगरेशन हो सकते हैं, तो उनकी अलग-अलग डायरेक्ट्री होनी चाहिए, ताकि दोनों का एक ही कार्रवाई का अलग वर्शन हो सके. ऐसा न होने पर, अगर दो कॉन्फ़िगरेशन एक ही आउटपुट फ़ाइल बनाने वाली कार्रवाई की कमांड लाइन से असहमत हों, तो बाज़ल को यह पता नहीं होता कि कौनसी कार्रवाई चुननी है
- अगर दो कॉन्फ़िगरेशन एक ही चीज़ को "मोटे तौर पर" दिखाते हैं, तो उनका नाम एक ही होना चाहिए, ताकि निर्देश कमांड मैच होने पर एक कार्रवाई को दूसरे के लिए फिर से इस्तेमाल किया जा सके: उदाहरण के लिए, Java कंपाइलर के कमांड लाइन विकल्पों में बदलाव की वजह से, C++ कंपाइल की कार्रवाइयां फिर से न चलाई जाएं.
अब तक, हमने इस समस्या को हल करने का एक सिद्धांत नहीं बनाया है. इसमें ट्रिमिंग ट्रिम करने की समस्या एक जैसी है. विकल्पों के बारे में ज़्यादा जानकारी यहां उपलब्ध है. समस्या वाले मुख्य क्षेत्र Starlark के नियम हैं (जिनके लेखक आम तौर पर Bazel से परिचित नहीं हैं) और अलग-अलग पहलू हैं. ये चीज़ें उन चीज़ों के स्पेस में एक और डाइमेंशन जोड़ते हैं जो "एक जैसी" आउटपुट फ़ाइल बना सकती हैं.
मौजूदा तरीका यह है कि कॉन्फ़िगरेशन के पाथ का सेगमेंट <CPU>-<compilation mode>
है जिसमें सफ़िक्स के साथ अलग-अलग सफ़िक्स जोड़े गए हैं. इससे, Java में लागू किए गए कॉन्फ़िगरेशन ट्रांज़िशन की वजह से कोई कार्रवाई नहीं होती है. इसके अलावा, Starlark कॉन्फ़िगरेशन
के ट्रांज़िशन का एक सेट जोड़ा जाता है, ताकि उपयोगकर्ताओं के लिए
कार्रवाई को लेकर विवाद न हो. यह एकदम सही नहीं है. इसे OutputDirectories.buildMnemonic()
में लागू किया जाता है और यह हर कॉन्फ़िगरेशन फ़्रैगमेंट पर निर्भर करता है, जो आउटपुट डायरेक्ट्री के नाम में अपना हिस्सा जोड़ता है.
जांच
Bazel पर, जांच के लिए बेहतर सहायता मौजूद है. यह इन तरीकों से काम करता है:
- रिमोट तरीके से जांच करने की सुविधा, अगर रिमोट पर बैकएंड बैकएंड उपलब्ध है (
- एक साथ कई बार टेस्ट करना (डिफ़्लेक्स करने या टाइमिंग डेटा इकट्ठा करने के लिए)
- शार्डिंग टेस्ट (स्पीड के लिए कई प्रोसेस पर टेस्ट टेस्ट को अलग-अलग करने की सुविधा)
- फ़्लैकी टेस्ट फिर से किए जा रहे हैं
- टेस्ट सुइट को टेस्ट सुइट के हिसाब से ग्रुप में बांटना
टेस्ट, सामान्य तौर पर कॉन्फ़िगर किए गए ऐसे टारगेट होते हैं जिनमें TestProvider होता है, जो बताता है कि टेस्ट कैसे होना चाहिए:
- ऐसे आर्टफ़ैक्ट जिनकी बिल्डिंग की जांच की जा रही है. यह "कैश मेमोरी की स्थिति" फ़ाइल है, जिसमें क्रम से लगाया गया एक
TestResultData
मैसेज होता है - टेस्ट चलाए जाने की संख्या
- टेस्ट में भेजे जाने वाले शार्ड की संख्या
- टेस्ट चलाने के तरीके के बारे में कुछ पैरामीटर (जैसे, जांच का समय खत्म होना)
यह तय करना कि कौनसे टेस्ट चलाने हैं
किस जांच को चलाना है, यह तय करना एक जटिल प्रोसेस है.
सबसे पहले, टारगेट पैटर्न पार्स करने के दौरान, टेस्ट सुइट को बार-बार बड़ा किया जाता है. इस एक्सटेंशन
को TestsForTargetPatternFunction
में लागू किया गया है. कुछ हद तक बुरी बात यह है कि अगर कोई टेस्ट सुइट टेस्ट में शामिल होने की घोषणा नहीं करता है, तो उसके पैकेज में मौजूद सभी टेस्ट की जानकारी देता है. इसे Package.beforeBuild()
में लागू किया जाता है. इसके लिए, सुइट के नियमों की जांच करने के लिए, $implicit_tests
नाम का इंप्लिसिट एट्रिब्यूट जोड़ा जाता है.
फिर, कमांड लाइन विकल्पों के अनुसार आकार, टैग, टाइम आउट और भाषा के लिए फ़िल्टर किए जाते हैं. इसे TestFilter
में लागू किया जाता है और टारगेट पार्स करने के दौरान
TargetPatternPhaseFunction.determineTests()
से कॉल किया जाता है और
नतीजे को TargetPatternPhaseValue.getTestsToRunLabels()
में रखा जाता है. जिन नियम एट्रिब्यूट के लिए फ़िल्टर किया जा सकता है उन्हें कॉन्फ़िगर नहीं किया जा सकता. ऐसा इसलिए, क्योंकि यह विश्लेषण के चरण से पहले होता है, इसलिए कॉन्फ़िगरेशन उपलब्ध नहीं होता.
इसके बाद, इसे आगे BuildView.createResult()
में प्रोसेस किया जाता है: उन टारगेट को फ़िल्टर कर दिया जाता है जिनका
विश्लेषण नहीं हो पाया. साथ ही, टेस्ट को खास और
गैर-खास टेस्ट में बांट दिया जाता है. इसके बाद, इसे AnalysisResult
में डाला जाता है. इससेExecutionTool
को पता चलता है कि कौनसी जांच करनी है.
इस विस्तृत प्रक्रिया में कुछ पारदर्शिता देने के लिए, tests()
क्वेरी ऑपरेटर (TestsFunction
में लागू किया जाता है) यह बताने के लिए उपलब्ध है कि कमांड लाइन पर कोई खास लक्ष्य तय होने पर कौनसे परीक्षण किए जाते हैं. यह एक अफ़सोस की बात है कि यह फिर से लागू किया गया है. इसलिए, यह हो सकता है कि यह कई तरीकों से ऊपर बताई गई बातों से अलग हो.
जांच की जा रही है
जांच की प्रक्रिया का तरीका, कैश मेमोरी की स्थिति वाले आर्टफ़ैक्ट के लिए अनुरोध करना है. इससे
TestRunnerAction
एक्ज़ीक्यूट होता है, जिसके बाद
--test_strategy
कमांड लाइन विकल्प से चुने गए
TestActionContext
को कॉल किया जाता है. यह विकल्प, अनुरोध किए गए तरीके से
जांच करता है.
टेस्ट एक विस्तृत प्रोटोकॉल के अनुसार चलाए जाते हैं, जो एनवायरमेंट वैरिएबल का इस्तेमाल करके टेस्ट को यह बताता है कि उनसे क्या उम्मीद की जाती है. यहां यह जानकारी दी गई है कि बेज़ल में टेस्ट से क्या उम्मीद की जाती है और बेज़ल से क्या उम्मीद की जा सकती है. आसान शब्दों में, 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
है . अगर बेज़ल में --nobuild_tests_only
फ़्लैग को पास किया जाता है, तो यह जांच के अलावा बाइनरी और लाइब्रेरी के लिए भी जनरेट होता है.
बेसलाइन कवरेज अभी काम नहीं कर रहा है.
हम हर नियम के लिए कवरेज कलेक्शन के लिए दो तरह की फ़ाइलों को ट्रैक करते हैं: इंस्ट्रूमेंटेशन वाली फ़ाइलों का सेट और इंस्ट्रूमेंटेशन मेटाडेटा फ़ाइलों का सेट.
इंस्ट्रुमेंट्ड फ़ाइलों का सेट, सिर्फ़ फ़ाइलों का एक सेट होता है. ऑनलाइन कवरेज रनटाइम के लिए, रनटाइम के दौरान यह तय किया जा सकता है कि किन फ़ाइलों को इंस्ट्रुमेंट करना है. इसका इस्तेमाल बेसलाइन कवरेज लागू करने में भी किया जाता है.
इंस्ट्रूमेंटेशन मेटाडेटा फ़ाइलों का एक सेट होता है, जिसमें वे अतिरिक्त फ़ाइलें होती हैं जिनकी जांच की ज़रूरत होती है. व्यावहारिक तौर पर, इसमें रनटाइम के हिसाब से बनाई गई फ़ाइलें शामिल होती हैं. उदाहरण के लिए, gcc, कंपाइलेशन के दौरान .gcno फ़ाइलें छोड़ती है. अगर कवरेज मोड चालू है, तो ये जांच कार्रवाइयों के सेट में जोड़ दिए जाते हैं.
कवरेज इकट्ठा किया जा रहा है या नहीं, इसे
BuildConfiguration
में स्टोर किया जाता है. यह इसलिए आसान है, क्योंकि यह बिट के आधार पर
कार्रवाई और कार्रवाई ग्राफ़ को बदलने का आसान तरीका है. हालांकि, इसका मतलब यह भी है कि अगर यह बिट फ़्लिप होता है, तो सभी टारगेट का दोबारा विश्लेषण करना होगा (जैसे कि C++ को कवरेज इकट्ठा करने के लिए, अलग-अलग कंपाइलर विकल्पों की ज़रूरत होती है. ऐसा करने से, समस्या का हल हो जाता है).
कवरेज के लिए सहायता फ़ाइलें, इस बात पर निर्भर करती हैं कि किस लेबल का इस्तेमाल किया जा सकता है. इससे, लागू करने की नीति में बदलाव हो सकता है. आदर्श रूप से, इन अंतरों को हटा दिया जाएगा और हम उनमें से एक के हिसाब से मानक तय कर लेंगे.
हम एक "कवरेज रिपोर्ट" भी जनरेट करते हैं, जो बेज़ल कैंपेन के हर टेस्ट के लिए इकट्ठा की गई कवरेज को मर्ज करती है. इसे CoverageReportActionFactory
मैनेज करता है और BuildView.createResult()
से कॉल किया जाता है . इससे, लागू किए गए पहले टेस्ट के :coverage_report_generator
एट्रिब्यूट को देखकर, उन टूल को ऐक्सेस किया जा सकता है जिनकी उन्हें ज़रूरत है.
क्वेरी इंजन
बैज़ल की छोटी भाषा है, जिसका इस्तेमाल अलग-अलग ग्राफ़ के बारे में इससे कई तरह के सवाल पूछने के लिए किया जाता है. नीचे दिए गए क्वेरी टाइप दिखाए गए हैं:
bazel query
का इस्तेमाल टारगेट ग्राफ़ की जांच करने के लिए किया जाता हैbazel cquery
का इस्तेमाल, कॉन्फ़िगर किए गए टारगेट ग्राफ़ की जांच करने के लिए किया जाता हैbazel aquery
का इस्तेमाल ऐक्शन ग्राफ़ की जांच करने के लिए किया जाता है
इनमें से हर एक को, सब-क्लास AbstractBlazeQueryEnvironment
में लागू किया जाता है.
अतिरिक्त क्वेरी फ़ंक्शन QueryFunction
सब-क्लास करने के बाद भी किए जा सकते हैं. स्ट्रीमिंग क्वेरी के नतीजों को अनुमति देने के लिए, query2.engine.Callback
को कुछ डेटा स्ट्रक्चर में इकट्ठा करने के बजाय, QueryFunction
को भेजा जाता है. यह इसे उन नतीजों के लिए कहता है जिन्हें वह लौटाना चाहता है.
क्वेरी का नतीजा कई तरीकों से भेजा जा सकता है: लेबल, लेबल, और नियम की क्लास, एक्सएमएल, प्रोटोबब वगैरह. इन्हें OutputFormatter
की सब-क्लास के तौर पर लागू किया जाता है.
क्वेरी आउटपुट के कुछ फ़ॉर्मैट (प्रोटो में) की ज़रूरत इसलिए होती है, क्योंकि बैज़ेल को पैकेज लोडिंग से जुड़ी _all _the जानकारी देनी होती है. इससे, आउटपुट में अंतर होता है और यह तय किया जा सकता है कि किसी खास टारगेट में बदलाव हुआ है या नहीं. इस वजह से, एट्रिब्यूट की वैल्यू को क्रम से लगाया जा सकता है. इसी वजह से, एट्रिब्यूट के ऐसे कुछ एट्रिब्यूट होते हैं जिनमें स्टारलार्क वैल्यू वाले कॉम्प्लेक्स एट्रिब्यूट नहीं होते. सामान्य तरीका है कि किसी लेबल का इस्तेमाल करें और उस लेबल के साथ नियम में जटिल जानकारी जोड़ें. यह कोई संतोषजनक समाधान नहीं है और इस शर्त को हटाना बहुत अच्छा है.
मॉड्यूल सिस्टम
बैज़ल को बढ़ाया जा सकता है और इसमें मॉड्यूल जोड़े जा सकते हैं. हर मॉड्यूल को सब-क्लास होना चाहिए
BlazeModule
(यह नाम बज़ेल के इतिहास का एक अहम हिस्सा है, जिसे पहले ब्लेज़ कहा जाता था). साथ ही, हर कमांड के काम करने के दौरान कई तरह के इवेंट की जानकारी मिलती है.
आम तौर पर, इनका इस्तेमाल "नॉन-कोर" फ़ंक्शन के अलग-अलग हिस्सों को लागू करने के लिए किया जाता है. ये बैजल के सिर्फ़ कुछ वर्शन (जैसे, Google पर इस्तेमाल किए जाने वाले वर्शन) के लिए ज़रूरी हैं:
- रिमोट एक्ज़ीक्यूशन सिस्टम के लिए इंटरफ़ेस
- नए निर्देश
BlazeModule
एक्सटेंशन एक्सटेंशन जो सेट है वह कुछ हद तक मुश्किल है. इसे डिज़ाइन के अच्छे सिद्धांतों के उदाहरण के तौर पर इस्तेमाल न करें.
इवेंट बस
ब्लेज़ मॉड्यूल मुख्य तौर पर इवेंट बस की मदद से, बज़ेल के बाकी सदस्यों के साथ बातचीत करता है
(EventBus
): हर बिल्ड के लिए एक नया इंस्टेंस बनाया जाता है, जबकि बज़ेल के अलग-अलग हिस्सों में इवेंट पोस्ट किए जा सकते हैं. मॉड्यूल में सुनने वालों को उनकी पसंद के इवेंट के लिए रजिस्टर किया जा सकता है. उदाहरण के लिए, नीचे दी गई चीज़ों को इवेंट के तौर पर दिखाया जाता है:
- बनाए जाने वाले बिल्ड टारगेट की सूची तय कर ली गई है
(
TargetParsingCompleteEvent
) - टॉप-लेवल कॉन्फ़िगरेशन तय किए गए हैं
(
BuildConfigurationEvent
) - (
TargetCompleteEvent
) टारगेट बनाया गया या नहीं - टेस्ट चलाया गया (
TestAttempt
,TestSummary
)
इनमें से कुछ इवेंट, बेज़ल के बाहर
इवेंट बनाएं प्रोटोकॉल में दिखाए गए हैं (ये BuildEvent
हैं). इससे न सिर्फ़ BlazeModule
, बल्कि Ba क़र्ज़ की प्रक्रिया से बाहर की चीज़ें भी देखी जा सकती हैं. इन्हें या तो फ़ाइल के रूप में
ऐक्सेस किया जा सकता है, जिसमें प्रोटोकॉल मैसेज होते हैं या Bazel, इवेंट स्ट्रीम करने के लिए सर्वर (जिसे बिल्ड
इवेंट सेवा कहा जाता है) से कनेक्ट कर सकता है.
इसे build.lib.buildeventservice
और
build.lib.buildeventstream
Java पैकेज में लागू किया जाता है.
डेटा स्टोर करने की बाहरी जगहें
मूल रूप से जबकि, बेज़ल को मोनोरेपो (एक ही स्रोत वाली ऐसी पेड़ जिस पर सब कुछ बनाने की ज़रूरत होती है) में इस्तेमाल करने के लिए डिज़ाइन किया गया था. वहीं, बेज़ल एक ऐसी दुनिया में रहता है जहां ज़रूरी नहीं है कि यह हर जगह एक ही हो. "बाहरी डेटा स्टोर करने की जगह" एक ऐसा ऐब्स्ट्रैक्शन है जिसका इस्तेमाल इन दो दुनिया को जोड़ने के लिए किया जाता है. ये ऐसे कोड को दिखाती हैं जो निर्माण के लिए ज़रूरी होते हैं, लेकिन मुख्य सोर्स ट्री में नहीं.
Workspace की फ़ाइल
डेटा स्टोर करने की बाहरी जगह का सेट, WORKSPACE फ़ाइल को पार्स करके तय किया जाता है. उदाहरण के लिए, इस तरह का एलान:
local_repository(name="foo", path="/foo/bar")
@foo
डेटा स्टोर करने की जगह में नतीजे उपलब्ध हैं. इसमें कुछ दिक्कतें आती हैं. हालांकि, कोई ऐसा तरीका है जो Starlark फ़ाइलों में डेटा स्टोर करने के नए नियम तय करता है. इसका इस्तेमाल, नए Starlark कोड को लोड करने के लिए किया जा सकता है. इसका इस्तेमाल डेटा स्टोर करने के नए नियमों वगैरह के लिए किया जा सकता है...
इस मामले में, WORKSPACE फ़ाइल (WorkspaceFileFunction
में) की पार्सिंग को load()
में बांटकर अलग-अलग हिस्सों में बांटा गया है. चंक इंडेक्स से पता चलता है कि WorkspaceFileKey.getIndex()
और WorkspaceFileFunction
को कंप्यूट करने का मतलब है कि इंडेक्स X का मतलब है कि जब तक Xth load()
स्टेटमेंट से उसका मूल्यांकन न किया जाए.
डेटा स्टोर करने की जगह ढूंढी जा रही है
बेज़ल के लिए रिपॉज़िटरी का कोड उपलब्ध होने से पहले, उसे
फ़ेच करना ज़रूरी है. इससे बेज़ल, $OUTPUT_BASE/external/<repository name>
में डायरेक्ट्री बना रहा है.
डेटा स्टोर करने की जगह को फ़ेच करने का तरीका, नीचे दिए गए चरणों में पूरा होता है:
PackageLookupFunction
को पता चला कि उसे रिपॉज़िटरी की ज़रूरत है और वहRepositoryName
कोSkyKey
के तौर पर बनाता है, जोRepositoryLoaderFunction
को शुरू करता हैRepositoryLoaderFunction
अगर यह अनुरोध साफ़ तौर पर नहीं बताया गया है, तोRepositoryDelegatorFunction
RepositoryDelegatorFunction
को Workspace की फ़ाइल के हिस्सों में डेटा देखने के लिए कहा जाने वाला रिपॉज़िटरी नियम पता चलता है. ऐसा तब तक किया जाता है, जब तक कि अनुरोध की गई जगह न मिल जाए- सही
RepositoryFunction
मिला, जो रिपॉज़िटरी फ़ेच करने की प्रोसेस लागू करता है. यह या तो रिपॉज़िटरी का Starlark लागू करना है या Java के डेटा स्टोर करने की जगह के लिए हार्ड कोड किया गया मैप है.
डेटा स्टोर करने की कई लेयर हैं, क्योंकि रिपॉज़िटरी फ़ेच करना बहुत महंगा हो सकता है:
- डाउनलोड की गई फ़ाइलों के लिए एक कैश मेमोरी होती है, जिसे उनके चेकसम (
RepositoryCache
) के हिसाब से बनाया जाता है. इसके लिए ज़रूरी है कि चेकसम फ़ाइल WorkSPACE फ़ाइल में उपलब्ध हो, लेकिन फिर भी, यह सदस्य के तौर पर काम करने के लिए अच्छा होता है. इसे एक ही वर्कस्टेशन पर मौजूद हर Bazel सर्वर इंस्टेंस से शेयर किया जाता है. भले ही, वे किसी भी फ़ाइल फ़ोल्डर या आउटपुट बेस में चल रहे हों. $OUTPUT_BASE/external
के तहत हर डेटा स्टोर करने की जगह के लिए "मार्कर फ़ाइल" लिखी जाती है. इसमें उस नियम का चेकसम होता है जिसका इस्तेमाल इसे फ़ेच करने के लिए किया गया था. अगर Bazel सर्वर फिर से चालू हो जाता है और चेकसम बदलता नहीं है, तो उसे फिर से नहीं लाया जा सकता. इसेRepositoryDelegatorFunction.DigestWriter
में लागू किया गया है .--distdir
कमांड लाइन विकल्प, एक और कैश मेमोरी तय करता है. इसका इस्तेमाल आर्टफ़ैक्ट को डाउनलोड करने के लिए किया जाता है. यह एंटरप्राइज़ सेटिंग में काम आता है, जहां Bazel को इंटरनेट से रैंडम चीज़ें नहीं हासिल करनी चाहिए.DownloadManager
को लागू किया गया है .
रिपॉज़िटरी डाउनलोड हो जाने के बाद, आर्टफ़ैक्ट को सोर्स
आर्टफ़ैक्ट माना जाता है. इससे समस्या होती है, क्योंकि बैज़ल आम तौर पर stat() को कॉल करके, सोर्स आर्टफ़ैक्ट के अप-टू-डेट होने की जांच करता है.
जब डेटा स्टोर करने की जगह में बदलाव होते हैं, तो इन आर्टफ़ैक्ट की जानकारी अमान्य हो जाती है. इसलिए,
किसी बाहरी रिपॉज़िटरी (डेटा स्टोर की जगह) में आर्टफ़ैक्ट के लिए,
FileStateValue
को बाहरी डेटा स्टोर करने की जगह
पर निर्भर होना ज़रूरी है. ExternalFilesHelper
इसे मैनेज करता है.
मैनेज की गई डायरेक्ट्री
कभी-कभी, बाहरी रिपॉज़िटरी को फ़ाइल रूट में मौजूद फ़ाइलों में बदलाव करना पड़ता है (जैसे, डाउनलोड किए गए पैकेज को सोर्स ट्री की सबडायरेक्ट्री में रखने वाला पैकेज मैनेजर). यह माना जाता है कि बेज़ल यह मानता है कि स्रोत फ़ाइलें सिर्फ़ उपयोगकर्ता से बदली गई हैं, न कि अपने-आप. इसलिए, पैकेज को फ़ाइल फ़ोल्डर के रूट में मौजूद हर डायरेक्ट्री के लिए अनुमति दी जाती है. डेटा स्टोर करने की इस तरह की बाहरी जगह का काम करने के लिए, Bzel दो काम करता है:
- उपयोगकर्ता इस फ़ाइल फ़ोल्डर को जिस सब-डायरेक्ट्री के बारे में जानना चाहता है उसे तय करने की अनुमति
नहीं देता. वे
.bazelignore
नाम की फ़ाइल में शामिल होती हैं और यह सुविधाBlacklistedPackagePrefixesFunction
में लागू होती है. - हम फ़ाइल फ़ोल्डर की सबडायरेक्ट्री से बाहरी डेटा स्टोर करने की जगह में मैपिंग को एन्कोड करते हैं. इसे हैंडल करने का काम
ManagedDirectoriesKnowledge
में किया जाता है. साथ ही, इनका इस्तेमाल उनFileStateValue
के लिए किया जाता है जो सामान्य बाहर के डेटा स्टोर करने की जगह के लिए करते हैं.
रिपॉज़िटरी मैपिंग
ऐसा हो सकता है कि एक से ज़्यादा रिपॉज़िटरी एक ही डेटा स्टोर करने की जगह पर निर्भर होना चाहें,
लेकिन यह अलग-अलग वर्शन में हो (यह "डायमंड डिपेंडेंसी से जुड़ी समस्या" का इंस्टेंस है). उदाहरण के लिए, अगर बिल्ड में अलग-अलग रिपॉज़िटरी में मौजूद दो बाइनरी ग्वावा पर निर्भर रहना चाहती हैं, तो वे शायद @guava//
से शुरू होने वाले लेबल के साथ ग्वावा का हवाला देंगी. साथ ही, वे इसके अलग-अलग वर्शन होने की उम्मीद कर रही हैं.
इसलिए, Bazel एक को बाहरी रिपॉज़िटरी लेबल को फिर से मैप करने की अनुमति देता है, ताकि स्ट्रिंग @guava//
एक बाइनरी रिपॉज़िटरी (जैसे कि @guava1//
) को एक बाइनरी रिपॉज़िटरी (और @guava2//
) के डेटा स्टोर करने की जगह (रिपॉज़िटरी) में दिखा सके.
इसके अलावा, इसका इस्तेमाल हीरों को जोड़ने के लिए भी किया जा सकता है. अगर कोई रिपॉज़िटरी
@guava1//
पर निर्भर करती है और दूसरी @guava2//
पर निर्भर करती है, तो रिपॉज़िटरी मैपिंग, एक रिपॉज़िटरी को रिपॉज़िटरी, दोनों को कैननिकल @guava//
रिपॉज़िटरी का इस्तेमाल करने की अनुमति देती है.
मैपिंग को WORKSPACE फ़ाइल में अलग-अलग रिपॉज़िटरी परिभाषाओं की repo_mapping
विशेषता के रूप में बताया गया है. इसके बाद, यह Skyframe में
WorkspaceFileValue
के सदस्य के तौर पर दिखता है. यहां यह:
Package.Builder.repositoryMapping
. इसका इस्तेमाल पैकेज में नियमों के लेबल की गई विशेषताओं के एट्रिब्यूट को बदलने के लिए किया जाता हैRuleClass.populateRuleAttributeValues()
Package.repositoryMapping
का इस्तेमाल, विश्लेषण के चरण में किया जाता है. ऐसा$(location)
जैसी चीज़ों को ठीक करने के लिए किया जाता है, जिन्हें लोड होने के समय में पार्स नहीं किया जाता- लोड() स्टेटमेंट में लेबल से जुड़ी समस्याओं को हल करने के लिए
BzlLoadFunction
जेएनआई बिट
Bazel का सर्वर ज़्यादातर Java में _write है. हालांकि, इन हिस्सों को अपवाद के तौर पर इस्तेमाल नहीं किया जा सकता. या फिर, वे लागू नहीं किए जा सकते. इसका ज़्यादातर इस्तेमाल फ़ाइल सिस्टम, प्रोसेस कंट्रोल, और नीचे की कई दूसरी चीज़ों के साथ होता है.
C++ कोड, src/main/Native के नीचे और नेटिव तरीकों वाली Java क्लास होती हैं:
NativePosixFiles
औरNativePosixFileSystem
ProcessUtils
WindowsFileOperations
औरWindowsFileProcesses
com.google.devtools.build.lib.platform
कंसोल आउटपुट
कंसोल आउटपुट को छोड़ना आसान लग रहा है, लेकिन कई प्रोसेस (कभी-कभी दूर से) को चलाने का संगम, बेहतर कैश मेमोरी, अच्छा और रंग-बिरंगा टर्मिनल आउटपुट और लंबे समय तक चलने वाला सर्वर है, यह काम आसान नहीं है.
क्लाइंट के आरपीसी कॉल आने के ठीक बाद, RpcOutputStream
दो इंस्टेंस बनाए जाते हैं (stdout और stderr के लिए) जो क्लाइंट में प्रिंट किए गए डेटा को
आगे भेजते हैं. इसके बाद, इन्हें OutErr
(stdout, stderr)
की जोड़ी में रैप किया जाता है. कंसोल पर जो कुछ भी प्रिंट करने की ज़रूरत होती है वह इन स्ट्रीम से होता है. इसके बाद, ये स्ट्रीम
BlazeCommandDispatcher.execExclusively()
को दे दी जाती हैं.
आउटपुट को डिफ़ॉल्ट रूप से ANSI एस्केप सीक्वेंस के साथ प्रिंट किया जाता है. अगर यह न हो,
तो (--color=no
) उन्हें AnsiStrippingOutputStream
से निकाल दिया जाता है. इसके अलावा, System.out
और System.err
को इन आउटपुट स्ट्रीम पर रीडायरेक्ट किया जाता है.
ऐसा करने से डीबग करने की जानकारी System.err.println()
का इस्तेमाल करके प्रिंट की जा सकती है और फिर भी क्लाइंट के टर्मिनल आउटपुट (जो सर्वर से अलग होता है) पर पहुंच जाता है. इस बात का ध्यान रखना कि अगर कोई प्रोसेस बाइनरी आउटपुट तैयार करती है (जैसे कि bazel query --output=proto
), तो stdout का कोई इनपुट नहीं लिया जाता.
छोटे मैसेज (गड़बड़ियां, चेतावनियां, वगैरह)
EventHandler
इंटरफ़ेस की मदद से दिखाए जाते हैं. खास तौर पर, ये EventBus
वाली पोस्ट से अलग होते हैं (भ्रम की स्थिति में). हर Event
में एक EventKind
(गड़बड़ी, चेतावनी, जानकारी, और कुछ अन्य) होते हैं. साथ ही, हर साइट के लिए एक Location
हो सकता है (सोर्स कोड में मौजूद जगह, जहां इवेंट हुआ था).
कुछ EventHandler
लागू करने पर वे इवेंट सेव कर लेते हैं. इसका इस्तेमाल, कैश मेमोरी में सेव की गई अलग-अलग तरह की प्रोसेस की वजह से, यूज़र इंटरफ़ेस (यूआई) में जानकारी को फिर से चलाने के लिए किया जाता है. उदाहरण के लिए, कैश मेमोरी में कॉन्फ़िगर किए गए टारगेट से ट्रिगर हुई चेतावनियां.
कुछ EventHandler
इवेंट भी पोस्ट करने की अनुमति देते हैं जो आखिर में इवेंट बस का रास्ता ढूंढ लेते हैं (सामान्य Event
वहां _नहीं _दिखते हैं). ये ExtendedEventHandler
के लागू होते हैं. इनका मुख्य इस्तेमाल, कैश मेमोरी में सेव किए गए
EventBus
इवेंट को फिर से चलाने के लिए होता है. EventBus
इवेंट EventBus
लागू करते हैं. हालांकि, EventBus
में पोस्ट किया गया हर कॉन्टेंट, इस इंटरफ़ेस को लागू करता है;
इसकी ज़रूरत सिर्फ़ उन लोगों को होती है जो ExtendedEventHandler
(कैश मेमोरी) और ज़्यादातर चीज़ों को कैश मेमोरी में सेव करते हैं; हालांकि, उन्हें लागू नहीं किया जाता हैPostable
टर्मिनल आउटपुट ज़्यादातर UiEventHandler
से आता है. यह सभी फ़ैंसी आउटपुट फ़ॉर्मैटिंग और प्रोग्रेस रिपोर्टिंग के लिए, ज़िम्मेदार होता है. इसमें दो इनपुट होते हैं:
- इवेंट बस
- इवेंट स्ट्रीमर इसमें शामिल हुआ रिपोर्टर
कमांड एक्ज़ीक्यूशन मशीनरी (उदाहरण के लिए, बाज़ेल के बाक़ी) को क्लाइंट से आरपीसी स्ट्रीम से ही सीधे कनेक्ट किया जाता है. यह Reporter.getOutErr()
के ज़रिए होती है, जिससे इन स्ट्रीम का सीधा ऐक्सेस मिलता है. इसका इस्तेमाल सिर्फ़ तब किया जाता है, जब किसी कमांड को बड़ी मात्रा में बाइनरी डेटा (जैसे, bazel query
) को डंप करने की ज़रूरत होती है.
बज़ल प्रोफ़ाइलिंग
बाज़ल तेज़ है. बेज़ल भी धीमा होता है, क्योंकि बिल्ड तब तक बनते रहते हैं,
जब तक कि उनके वज़न को ठीक नहीं किया जा सकता. इस वजह से, Bazel में एक प्रोफ़ाइलर शामिल होता है, जिसका इस्तेमाल प्रोफ़ाइल बिल्ड और Bazel में किया जा सकता है. यह एक ऐसी क्लास में लागू होता है जिसका नाम Profiler
है. यह डिफ़ॉल्ट रूप से चालू होती है. हालांकि, यह सिर्फ़ छोटे किए गए डेटा को रिकॉर्ड करता है
ताकि इसका ओवरहेड सहन किया जा सके. कमांड लाइन --record_full_profiler_data
इसे हर चीज़ रिकॉर्ड करती है.
इससे Chrome के प्रोफ़ाइलर फ़ॉर्मैट में प्रोफ़ाइल आती है. Chrome में इसे सबसे अच्छे तरीके से देखा जाता है. इसका डेटा मॉडल, टास्क स्टैक का है: एक टास्क शुरू कर सकता है और टास्क खत्म कर सकता है. साथ ही, उन्हें एक-दूसरे के अंदर साफ़ तौर पर नेस्ट करना होता है. हर Java थ्रेड को अपना टास्क स्टैक मिलता है. TODO: यह कार्रवाइयों और कंटिन्यूशन पास करने की स्टाइल के साथ कैसे काम करता है?
प्रोफ़ाइलर को शुरू करके BlazeRuntime.initProfiler()
में
BlazeRuntime.afterCommand()
रोका गया है. साथ ही, वह ज़्यादा से ज़्यादा समय तक लाइव रखने की कोशिश करेगा, ताकि हम सब कुछ प्रोफ़ाइल कर सकें. प्रोफ़ाइल में कुछ जोड़ने के लिए, Profiler.instance().profile()
पर कॉल करें. यह एक Closeable
दिखाता है, जिसके बंद होने से टास्क पूरा होता है. सबसे सही तरीका है 'संसाधन के लिए आज़माना' स्टेटमेंट
के साथ.
हम MemoryProfiler
में, अल्पसंख्यक मेमोरी की प्रोफ़ाइलिंग भी करते हैं. यह हमेशा चालू ही रहता है
और ज़्यादातर हीप साइज़ और जीसी व्यवहार को भी रिकॉर्ड करता है.
बेज़ल की जाँच
Bazel में दो मुख्य तरह के टेस्ट होते हैं: पहला, "B परीक्षण" और "काला बॉक्स" के तौर पर बनाए गए टेस्ट और सिर्फ़ विश्लेषण के चरण चलाने वाले. हम पुराने "इंटिग्रेशन टेस्ट" और उसे बाद में मिलने वाले "यूनिट टेस्ट" कहते हैं. हालांकि, ये इंटिग्रेशन टेस्ट जैसा ही होते हैं, लेकिन वे कम इंटिग्रेट होते हैं. हमारे पास कुछ असल यूनिट टेस्ट भी हैं, जो ज़रूरी हैं.
इंटिग्रेशन की जांच करने के दो तरीके हैं:
- उदाहरण:
src/test/shell
- Java में लागू किए गए पैरामीटर. इन्हें
BuildIntegrationTestCase
की सब-क्लास के तौर पर लागू किया जाता है
BuildIntegrationTestCase
, इंटिग्रेशन की जांच करने के लिए सबसे अच्छा फ़्रेमवर्क है. ऐसा इसलिए है, क्योंकि यह
ज़्यादातर टेस्टिंग की स्थितियों में काम करता है. यह एक Java फ़्रेमवर्क है, जो डीबग करने की क्षमता और कई सामान्य डेवलपमेंट टूल के साथ आसानी से इंटिग्रेशन की सुविधा देता है. BuildIntegrationTestCase
विश्लेषण की जांचों को BuildViewTestCase
की सब-क्लास के रूप में लागू किया जाता है. आप स्क्रैच फ़ाइल सिस्टम की मदद से BUILD
फ़ाइलें लिख सकते हैं. इसके बाद, कॉन्फ़िगर करने के टारगेट के लिए,
सहायता करने वाले कई तरीके कॉन्फ़िगर कर सकते हैं. साथ ही, कॉन्फ़िगरेशन बदल सकते हैं और
विश्लेषण के नतीजे के बारे में अलग-अलग बातें बता सकते हैं.