इस पेज पर, पहलू इस्तेमाल करने के फ़ायदों और बुनियादी बातों के बारे में बताया गया है. साथ ही, इसमें आसान और ऐडवांस उदाहरण दिए गए हैं.
ऐस्पेक्ट की मदद से, बिल्ड डिपेंडेंसी ग्राफ़ में अतिरिक्त जानकारी और कार्रवाइयां जोड़ी जा सकती हैं. यहां कुछ सामान्य उदाहरण दिए गए हैं, जिनमें पहलुओं का इस्तेमाल किया जा सकता है:
- Bazel के साथ इंटिग्रेट किए गए IDE, प्रोजेक्ट के बारे में जानकारी इकट्ठा करने के लिए पहलुओं का इस्तेमाल कर सकते हैं.
- कोड जनरेट करने वाले टूल, पहलुओं का इस्तेमाल करके अपने इनपुट को टारगेट-अग्नोस्टिक तरीके से लागू कर सकते हैं. उदाहरण के लिए,
BUILD
फ़ाइलें, protobuf लाइब्रेरी की परिभाषाओं का क्रम तय कर सकती हैं. साथ ही, भाषा के हिसाब से बने नियम, पहलुओं का इस्तेमाल करके ऐसी कार्रवाइयां जोड़ सकते हैं जो किसी भाषा के लिए protobuf सपोर्ट कोड जनरेट करती हैं.
आसपेक्ट से जुड़ी बुनियादी बातें
BUILD
फ़ाइलों में, किसी प्रोजेक्ट के सोर्स कोड के बारे में जानकारी होती है. जैसे, प्रोजेक्ट में कौनसी सोर्स फ़ाइलें शामिल हैं, उन फ़ाइलों से कौनसे आर्टफ़ैक्ट (टारगेट) बनाए जाने चाहिए, उन फ़ाइलों के बीच कौनसी डिपेंडेंसी हैं वगैरह. Bazel इस जानकारी का इस्तेमाल करके, बिल्ड बनाता है. इसका मतलब है कि यह आर्टफ़ैक्ट बनाने के लिए ज़रूरी कार्रवाइयों का सेट तय करता है. जैसे, कंपाइलर या लिंकर चलाना और उन कार्रवाइयों को पूरा करना. Bazel, टारगेट के बीच डिपेंडेंसी ग्राफ़ बनाकर ऐसा करता है. साथ ही, उन कार्रवाइयों को इकट्ठा करने के लिए इस ग्राफ़ पर जाता है.
यहां दी गई BUILD
फ़ाइल देखें:
java_library(name = 'W', ...)
java_library(name = 'Y', deps = [':W'], ...)
java_library(name = 'Z', deps = [':W'], ...)
java_library(name = 'Q', ...)
java_library(name = 'T', deps = [':Q'], ...)
java_library(name = 'X', deps = [':Y',':Z'], runtime_deps = [':T'], ...)
इस BUILD
फ़ाइल में, यहां दी गई इमेज में दिखाया गया डिपेंडेंसी ग्राफ़ तय किया गया है:
पहली इमेज. BUILD
फ़ाइल डिपेंडेंसी ग्राफ़.
Bazel, इस डिपेंडेंसी ग्राफ़ का विश्लेषण करता है. इसके लिए, वह ऊपर दिए गए उदाहरण में हर टारगेट के लिए, उससे जुड़े नियम (इस मामले में "java_library") के लागू करने वाले फ़ंक्शन को कॉल करता है. नियम लागू करने वाले फ़ंक्शन, ऐसी कार्रवाइयां जनरेट करते हैं जिनसे .jar
फ़ाइलों जैसे आर्टफ़ैक्ट बनाए जाते हैं. साथ ही, providers में उन टारगेट की रिवर्स डिपेंडेंसी को जगह की जानकारी और उन आर्टफ़ैक्ट के नाम जैसी जानकारी पास की जाती है.
पहलू, नियमों की तरह ही होते हैं. इनमें लागू करने वाला एक फ़ंक्शन होता है, जो कार्रवाइयां जनरेट करता है और सेवा देने वाली कंपनियों की जानकारी दिखाता है. हालांकि, इनकी ताकत इस बात पर निर्भर करती है कि इनके लिए डिपेंडेंसी ग्राफ़ कैसे बनाया गया है. किसी पहलू में एक लागू करने की सुविधा होती है. साथ ही, इसमें उन सभी एट्रिब्यूट की सूची होती है जिन्हें यह आगे बढ़ाता है. मान लें कि एक आसपेक्ट A है, जो "deps" नाम के एट्रिब्यूट के साथ लागू होता है. इस पहलू को टारगेट X पर लागू किया जा सकता है. इससे पहलू को लागू करने वाला नोड A(X) मिलता है. इसे लागू करने के दौरान, पहलू A को उन सभी टारगेट पर बार-बार लागू किया जाता है जिन्हें X अपने "deps" एट्रिब्यूट (A की प्रोपगेशन सूची में मौजूद सभी एट्रिब्यूट) में रेफ़र करता है.
इसलिए, टारगेट X पर पहलू A लागू करने से, टारगेट के ओरिजनल डिपेंडेंसी ग्राफ़ का "शैडो ग्राफ़" मिलता है. इसे इस इमेज में दिखाया गया है:
दूसरी इमेज. पहलू के साथ ग्राफ़ बनाना.
सिर्फ़ उन किनारों पर शैडो होती है जो प्रोपगेशन सेट में मौजूद एट्रिब्यूट के साथ होते हैं. इसलिए, इस उदाहरण में runtime_deps
किनारे पर शैडो नहीं है. इसके बाद, शैडो ग्राफ़ के सभी नोड पर, पहलू लागू करने वाले फ़ंक्शन को कॉल किया जाता है. यह ठीक उसी तरह होता है जैसे मूल ग्राफ़ के नोड पर, नियम लागू करने वाले फ़ंक्शन को कॉल किया जाता है.
सामान्य उदाहरण
इस उदाहरण में, किसी नियम और उसकी उन सभी डिपेंडेंसी के लिए सोर्स फ़ाइलों को बार-बार प्रिंट करने का तरीका बताया गया है जिनमें deps
एट्रिब्यूट है. इसमें, ऐस्पेक्ट को लागू करने, ऐस्पेक्ट की परिभाषा, और Bazel कमांड लाइन से ऐस्पेक्ट को लागू करने का तरीका दिखाया गया है.
def _print_aspect_impl(target, ctx):
# Make sure the rule has a srcs attribute.
if hasattr(ctx.rule.attr, 'srcs'):
# Iterate through the files that make up the sources and
# print their paths.
for src in ctx.rule.attr.srcs:
for f in src.files.to_list():
print(f.path)
return []
print_aspect = aspect(
implementation = _print_aspect_impl,
attr_aspects = ['deps'],
)
आइए, उदाहरण को अलग-अलग हिस्सों में बांटकर, हर हिस्से की अलग-अलग जांच करें.
आस्पेक्ट की परिभाषा
print_aspect = aspect(
implementation = _print_aspect_impl,
attr_aspects = ['deps'],
)
आस्पेक्ट की परिभाषाएं, नियम की परिभाषाओं की तरह ही होती हैं. इन्हें aspect
फ़ंक्शन का इस्तेमाल करके तय किया जाता है.
किसी नियम की तरह, किसी पहलू में भी एक फ़ंक्शन होता है. इस मामले में, यह फ़ंक्शन _print_aspect_impl
है.
attr_aspects
, नियम एट्रिब्यूट की एक सूची है. इसके हिसाब से, पहलू आगे बढ़ता है.
इस मामले में, यह आसपेक्ट उन नियमों के deps
एट्रिब्यूट के साथ लागू होगा जिन पर इसे लागू किया गया है.
attr_aspects
के लिए एक और सामान्य तर्क ['*']
है. इससे किसी नियम के सभी एट्रिब्यूट में पहलू को फैलाया जा सकेगा.
आस्पेक्ट लागू करना
def _print_aspect_impl(target, ctx):
# Make sure the rule has a srcs attribute.
if hasattr(ctx.rule.attr, 'srcs'):
# Iterate through the files that make up the sources and
# print their paths.
for src in ctx.rule.attr.srcs:
for f in src.files.to_list():
print(f.path)
return []
पहलू लागू करने वाले फ़ंक्शन, नियम लागू करने वाले फ़ंक्शन की तरह ही होते हैं. ये providers को वापस भेजते हैं, actions जनरेट कर सकते हैं, और दो आर्ग्युमेंट लेते हैं:
target
: यह वह टारगेट है जिस पर पहलू लागू किया जा रहा है.ctx
:ctx
ऑब्जेक्ट का इस्तेमाल एट्रिब्यूट ऐक्सेस करने, आउटपुट जनरेट करने, और कार्रवाइयां करने के लिए किया जा सकता है.
लागू करने वाला फ़ंक्शन, टारगेट नियम के एट्रिब्यूट को ctx.rule.attr
के ज़रिए ऐक्सेस कर सकता है. यह उन प्रोवाइडर की जांच कर सकता है जिन्हें टारगेट ने उपलब्ध कराया है. टारगेट पर इसे target
आर्ग्युमेंट के ज़रिए लागू किया जाता है.
सेवा देने वाली कंपनियों की सूची दिखाने के लिए, पहलुओं की जानकारी देना ज़रूरी है. इस उदाहरण में, पहलू से कोई जानकारी नहीं मिलती है. इसलिए, यह एक खाली सूची दिखाता है.
कमांड लाइन का इस्तेमाल करके, पहलू को चालू करना
--aspects
आर्ग्युमेंट का इस्तेमाल करके, कमांड लाइन से किसी पहलू को लागू करना सबसे आसान तरीका है. मान लें कि ऊपर दिए गए आसपेक्ट रेशियो को print.bzl
नाम की फ़ाइल में इस तरह से तय किया गया है:
bazel build //MyExample:example --aspects print.bzl%print_aspect
print_aspect
को टारगेट example
और deps
एट्रिब्यूट के ज़रिए बार-बार ऐक्सेस किए जा सकने वाले सभी टारगेट नियमों पर लागू करेगा.
--aspects
फ़्लैग एक तर्क लेता है, जो <extension file label>%<aspect top-level name>
फ़ॉर्मैट में पहलू की जानकारी देता है.
ऐडवांस उदाहरण
यहां दिए गए उदाहरण में, टारगेट रूल के किसी पहलू का इस्तेमाल करने का तरीका दिखाया गया है. यह पहलू, टारगेट में मौजूद फ़ाइलों की गिनती करता है. साथ ही, फ़ाइलों को एक्सटेंशन के हिसाब से फ़िल्टर भी कर सकता है. इसमें बताया गया है कि वैल्यू वापस लाने के लिए, किसी प्रोवाइडर का इस्तेमाल कैसे किया जाता है. साथ ही, किसी पहलू को लागू करने के लिए, पैरामीटर का इस्तेमाल करके कोई तर्क कैसे पास किया जाता है. इसके अलावा, इसमें यह भी बताया गया है कि किसी नियम से किसी पहलू को कैसे लागू किया जाता है.
file_count.bzl
फ़ाइल:
FileCountInfo = provider(
fields = {
'count' : 'number of files'
}
)
def _file_count_aspect_impl(target, ctx):
count = 0
# Make sure the rule has a srcs attribute.
if hasattr(ctx.rule.attr, 'srcs'):
# Iterate through the sources counting files
for src in ctx.rule.attr.srcs:
for f in src.files.to_list():
if ctx.attr.extension == '*' or ctx.attr.extension == f.extension:
count = count + 1
# Get the counts from our dependencies.
for dep in ctx.rule.attr.deps:
count = count + dep[FileCountInfo].count
return [FileCountInfo(count = count)]
file_count_aspect = aspect(
implementation = _file_count_aspect_impl,
attr_aspects = ['deps'],
attrs = {
'extension' : attr.string(values = ['*', 'h', 'cc']),
}
)
def _file_count_rule_impl(ctx):
for dep in ctx.attr.deps:
print(dep[FileCountInfo].count)
file_count_rule = rule(
implementation = _file_count_rule_impl,
attrs = {
'deps' : attr.label_list(aspects = [file_count_aspect]),
'extension' : attr.string(default = '*'),
},
)
BUILD.bazel
फ़ाइल:
load('//:file_count.bzl', 'file_count_rule')
cc_library(
name = 'lib',
srcs = [
'lib.h',
'lib.cc',
],
)
cc_binary(
name = 'app',
srcs = [
'app.h',
'app.cc',
'main.cc',
],
deps = ['lib'],
)
file_count_rule(
name = 'file_count',
deps = ['app'],
extension = 'h',
)
आस्पेक्ट की परिभाषा
file_count_aspect = aspect(
implementation = _file_count_aspect_impl,
attr_aspects = ['deps'],
attrs = {
'extension' : attr.string(values = ['*', 'h', 'cc']),
}
)
इस उदाहरण में दिखाया गया है कि deps
एट्रिब्यूट के ज़रिए, पहलू कैसे फैलता है.
attrs
किसी पहलू के लिए एट्रिब्यूट का सेट तय करता है. सार्वजनिक आसपेक्ट एट्रिब्यूट, पैरामीटर तय करते हैं. ये सिर्फ़ bool
, int
या string
टाइप के हो सकते हैं.
नियम के हिसाब से लागू होने वाले पहलुओं के लिए, int
और string
पैरामीटर में values
तय होना चाहिए. इस उदाहरण में, extension
नाम का एक पैरामीटर है. इसकी वैल्यू '*
', 'h
' या 'cc
' हो सकती है.
नियम के हिसाब से तय किए गए आसपेक्ट के लिए, पैरामीटर की वैल्यू उस नियम से ली जाती हैं जो आसपेक्ट का अनुरोध करता है. इसके लिए, नियम के उस एट्रिब्यूट का इस्तेमाल किया जाता है जिसका नाम और टाइप एक जैसा होता है.
(file_count_rule
की परिभाषा देखें).
कमांड-लाइन के पहलुओं के लिए, पैरामीटर की वैल्यू को --aspects_parameters
फ़्लैग का इस्तेमाल करके पास किया जा सकता है. int
और string
पैरामीटर पर लगी values
पाबंदी को हटाया जा सकता है.
पहलू में label
या label_list
टाइप के निजी एट्रिब्यूट भी हो सकते हैं. प्राइवेट लेबल एट्रिब्यूट का इस्तेमाल, उन टूल या लाइब्रेरी के बारे में बताने के लिए किया जा सकता है जिनकी ज़रूरत, पहलुओं से जनरेट होने वाली कार्रवाइयों के लिए होती है. इस उदाहरण में कोई निजी एट्रिब्यूट तय नहीं किया गया है. हालांकि, यहां दिए गए कोड स्निपेट से पता चलता है कि किसी पहलू में टूल को कैसे पास किया जा सकता है:
...
attrs = {
'_protoc' : attr.label(
default = Label('//tools:protoc'),
executable = True,
cfg = "exec"
)
}
...
आस्पेक्ट लागू करना
FileCountInfo = provider(
fields = {
'count' : 'number of files'
}
)
def _file_count_aspect_impl(target, ctx):
count = 0
# Make sure the rule has a srcs attribute.
if hasattr(ctx.rule.attr, 'srcs'):
# Iterate through the sources counting files
for src in ctx.rule.attr.srcs:
for f in src.files.to_list():
if ctx.attr.extension == '*' or ctx.attr.extension == f.extension:
count = count + 1
# Get the counts from our dependencies.
for dep in ctx.rule.attr.deps:
count = count + dep[FileCountInfo].count
return [FileCountInfo(count = count)]
नियम लागू करने वाले फ़ंक्शन की तरह, आसपेक्ट लागू करने वाला फ़ंक्शन, सेवा देने वाली कंपनियों का एक स्ट्रक्चर दिखाता है. इसे इसकी डिपेंडेंसी ऐक्सेस कर सकती हैं.
इस उदाहरण में, FileCountInfo
को एक ऐसी सेवा देने वाली कंपनी के तौर पर तय किया गया है जिसके पास एक फ़ील्ड count
है. fields
एट्रिब्यूट का इस्तेमाल करके, किसी प्रोवाइडर के फ़ील्ड को साफ़ तौर पर तय करना सबसे सही तरीका है.
किसी ऐप्लिकेशन A(X) के लिए, सेवा देने वाली कंपनियों का सेट, सेवा देने वाली उन कंपनियों का यूनियन होता है जो टारगेट X के लिए नियम लागू करने और ऐप्लिकेशन A को लागू करने से मिलती हैं. नियम लागू करने वाले प्रोवाइडर, पहलुओं को लागू करने से पहले बनाए और फ़्रीज़ किए जाते हैं. इन्हें किसी पहलू से बदला नहीं जा सकता. अगर टारगेट और उस पर लागू होने वाला पहलू, दोनों एक ही तरह के प्रोवाइडर को जानकारी देते हैं, तो यह गड़बड़ी होती है. हालांकि, OutputGroupInfo
(जिसे मर्ज किया जाता है, बशर्ते नियम और पहलू अलग-अलग आउटपुट ग्रुप के बारे में बताते हों) और InstrumentedFilesInfo
(जिसे पहलू से लिया जाता है) को छोड़कर, ऐसा नहीं होना चाहिए. इसका मतलब है कि पहलू लागू करने पर, कभी भी DefaultInfo
नहीं मिल सकता.
पैरामीटर और निजी एट्रिब्यूट, ctx
के एट्रिब्यूट में पास किए जाते हैं. इस उदाहरण में, extension
पैरामीटर का रेफ़रंस दिया गया है. साथ ही, यह तय किया गया है कि किन फ़ाइलों को गिना जाए.
सेवा देने वाली कंपनियों के लिए, जिन एट्रिब्यूट की वैल्यू के साथ पहलू को आगे बढ़ाया जाता है (attr_aspects
सूची से), उन्हें पहलू के नतीजों से बदल दिया जाता है. उदाहरण के लिए, अगर टारगेट X की डिपेंडेंसी में Y और Z हैं, तो A(X) के लिए ctx.rule.attr.deps
[A(Y), A(Z)] होगा.
इस उदाहरण में, ctx.rule.attr.deps
टारगेट ऑब्जेक्ट हैं. ये टारगेट ऑब्जेक्ट, ओरिजनल टारगेट के 'deps' पर पहलू लागू करने के नतीजे हैं. पहलू को ओरिजनल टारगेट पर लागू किया गया है.
उदाहरण में, पहलू, फ़ाइलों की कुल संख्या इकट्ठा करने के लिए, टारगेट की डिपेंडेंसी से FileCountInfo
प्रोवाइडर को ऐक्सेस करता है.
किसी नियम से पहलू को लागू करना
def _file_count_rule_impl(ctx):
for dep in ctx.attr.deps:
print(dep[FileCountInfo].count)
file_count_rule = rule(
implementation = _file_count_rule_impl,
attrs = {
'deps' : attr.label_list(aspects = [file_count_aspect]),
'extension' : attr.string(default = '*'),
},
)
नियम लागू करने के इस उदाहरण में, ctx.attr.deps
के ज़रिए FileCountInfo
को ऐक्सेस करने का तरीका बताया गया है.
नियम की परिभाषा से पता चलता है कि पैरामीटर (extension
) को कैसे तय किया जाता है और उसे डिफ़ॉल्ट वैल्यू (*
) कैसे दी जाती है. ध्यान दें कि डिफ़ॉल्ट वैल्यू के तौर पर 'cc
', 'h
' या '*
' के अलावा किसी और वैल्यू का इस्तेमाल करने पर गड़बड़ी होगी. ऐसा इसलिए, क्योंकि आसपेक्ट की परिभाषा में पैरामीटर पर पाबंदियां लगाई गई हैं.
टारगेट करने के नियम के ज़रिए किसी पहलू को लागू करना
load('//:file_count.bzl', 'file_count_rule')
cc_binary(
name = 'app',
...
)
file_count_rule(
name = 'file_count',
deps = ['app'],
extension = 'h',
)
इसमें दिखाया गया है कि नियम के ज़रिए, extension
पैरामीटर को पहलू में कैसे पास किया जाता है. नियम लागू करने के दौरान, extension
पैरामीटर की डिफ़ॉल्ट वैल्यू होती है. इसलिए, extension
को एक वैकल्पिक पैरामीटर माना जाएगा.
file_count
टारगेट बनने के बाद, हमारे आसपेक्ट का आकलन किया जाएगा. साथ ही, deps
के ज़रिए बार-बार ऐक्सेस किए जा सकने वाले सभी टारगेट का आकलन किया जाएगा.