पहलू

इस पेज पर, आसपेक्ट इस्तेमाल करने की बुनियादी बातें और फ़ायदों के बारे में बताया गया है. साथ ही, इसमें आसान और मुश्किल उदाहरण भी दिए गए हैं.

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

  • Bazel के साथ इंटिग्रेट होने वाले आईडीई, प्रोजेक्ट के बारे में जानकारी इकट्ठा करने के लिए आसपेक्ट का इस्तेमाल कर सकते हैं.
  • कोड जनरेट करने वाले टूल, टारगेट-अग्नोस्टिक तरीके से अपने इनपुट पर कार्रवाई करने के लिए, आसपेक्ट का इस्तेमाल कर सकते हैं. 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 फ़ाइलें. साथ ही, ये फ़ंक्शन, उन आर्टफ़ैक्ट की जगहों और नामों जैसी जानकारी, प्रोवाइडर में उन टारगेट की रिवर्स डिपेंडेंसी को पास करते हैं.

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

इसलिए, किसी टारगेट X पर आसपेक्ट A को लागू करने से, टारगेट के ओरिजनल डिपेंडेंसी ग्राफ़ का "शैडो ग्राफ़" मिलता है. यह ग्राफ़, यहां दी गई इमेज में दिखाया गया है:

Aspect की मदद से ग्राफ़ बनाना

दूसरी इमेज. आसपेक्ट के साथ बिल्ड ग्राफ़.

शैडो सिर्फ़ उन किनारों पर पड़ता है जो प्रोपगेशन सेट में मौजूद एट्रिब्यूट के साथ होते हैं. इसलिए, इस उदाहरण में 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 के लिए एक और सामान्य आर्ग्युमेंट ['*'] है. इससे, आसपेक्ट को किसी नियम के सभी ए101ट्रिब्यूट पर लागू किया जा सकेगा.

आसपेक्ट लागू करना

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 []

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

  • 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 के deps में 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 के ज़रिए बार-बार ऐक्सेस किया जा सकता है.

रेफ़रंस