मैक्रो

इस पेज पर, मैक्रो इस्तेमाल करने के बुनियादी तरीके के बारे में बताया गया है. साथ ही, इसमें इस्तेमाल के सामान्य उदाहरण, डीबग करने का तरीका, और कॉन्वेंशन शामिल हैं.

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

मैक्रो दो तरह के होते हैं: सिंबल मैक्रो, जिनके बारे में इस पेज पर बताया गया है और लेगसी मैक्रो. हमारा सुझाव है कि जहां भी हो सके, कोड को साफ़ तौर पर समझने के लिए, सिंबल मैक्रो का इस्तेमाल करें.

सिंबल वाले मैक्रो, टाइप किए गए आर्ग्युमेंट (स्ट्रिंग से लेबल में कन्वर्ज़न, मैक्रो को कॉल करने की जगह के हिसाब से) और बनाए गए टारगेट की दिखने की सुविधा को सीमित करने और तय करने की सुविधा देते हैं. इन्हें धीरे-धीरे जांचने के लिए डिज़ाइन किया गया है. यह सुविधा, Bazel के आने वाले वर्शन में जोड़ी जाएगी. सिंबल मैक्रो, Bazel 8 में डिफ़ॉल्ट रूप से उपलब्ध होते हैं. इस दस्तावेज़ में macros का उल्लेख, सिंबल मैक्रो के बारे में बताने के लिए किया गया है.

इस्तेमाल

मैक्रो, .bzl फ़ाइलों में तय किए जाते हैं. इसके लिए, macro() फ़ंक्शन को दो ज़रूरी पैरामीटर के साथ कॉल किया जाता है: attrs और implementation.

विशेषताएं

attrs, एट्रिब्यूट के नाम से एट्रिब्यूट टाइप की डिक्शनरी स्वीकार करता है. यह मैक्रो के लिए आर्ग्युमेंट दिखाता है. दो सामान्य एट्रिब्यूट – name और visibility – को सभी मैक्रो में अपने-आप जोड़ दिया जाता है. साथ ही, इन्हें attrs को पास की गई डिक्शनरी में शामिल नहीं किया जाता.

# macro/macro.bzl
my_macro = macro(
    attrs = {
        "deps": attr.label_list(mandatory = True, doc = "The dependencies passed to the inner cc_binary and cc_test targets"),
        "create_test": attr.bool(default = False, configurable = False, doc = "If true, creates a test target"),
    },
    implementation = _my_macro_impl,
)

एट्रिब्यूट टाइप के एलान में, पैरामीटर, mandatory, default, और doc का इस्तेमाल किया जा सकता है. ज़्यादातर एट्रिब्यूट टाइप में configurable पैरामीटर भी काम करता है. इससे यह तय होता है कि एट्रिब्यूट में select इस्तेमाल किए जा सकते हैं या नहीं. अगर कोई एट्रिब्यूट configurable है, तो वह select के अलावा किसी दूसरी वैल्यू को, कॉन्फ़िगर नहीं की जा सकने वाली select के तौर पर पार्स करेगा – "foo", select({"//conditions:default": "foo"}) बन जाएगा. ज़्यादा जानकारी के लिए, चुनें पर जाएं.

एट्रिब्यूट इनहेरिटेंस

अहम जानकारी: एट्रिब्यूट इनहेरिटेंस, एक्सपेरिमेंट के तौर पर उपलब्ध एक सुविधा है. इसे --experimental_enable_macro_inherit_attrs फ़्लैग की मदद से चालू किया जाता है. इस सेक्शन में बताए गए कुछ व्यवहार, डिफ़ॉल्ट रूप से चालू होने से पहले बदल सकते हैं.

मैक्रो का मकसद अक्सर किसी नियम (या किसी दूसरे मैक्रो) को रैप करना होता है. साथ ही, मैक्रो का लेखक अक्सर **kwargs का इस्तेमाल करके, मैक्रो के मुख्य टारगेट (या मुख्य इनर मैक्रो) पर, रैप किए गए सिंबल के ज़्यादातर एट्रिब्यूट को बिना बदलाव के फ़ॉरवर्ड करना चाहता है.

इस पैटर्न के साथ काम करने के लिए, कोई मैक्रो किसी नियम या किसी दूसरे मैक्रो से एट्रिब्यूट इनहेरिट कर सकता है. इसके लिए, macro() के inherit_attrs आर्ग्युमेंट में नियम या मैक्रो सिंबल डालना होगा. (सभी Starlark बिल्ड नियमों के लिए तय किए गए सामान्य एट्रिब्यूट को इनहेरिट करने के लिए, नियम या मैक्रो सिंबल के बजाय, खास स्ट्रिंग "common" का भी इस्तेमाल किया जा सकता है.) सिर्फ़ सार्वजनिक एट्रिब्यूट इनहेरिट किए जाते हैं. साथ ही, मैक्रो की अपनी attrs डिक्शनरी में मौजूद एट्रिब्यूट, एक ही नाम वाले इनहेरिट किए गए एट्रिब्यूट को बदल देते हैं. attrs डिक्शनरी में वैल्यू के तौर पर None का इस्तेमाल करके, इनहेरिट किए गए एट्रिब्यूट को हटाया भी जा सकता है:

# macro/macro.bzl
my_macro = macro(
    inherit_attrs = native.cc_library,
    attrs = {
        # override native.cc_library's `local_defines` attribute
        local_defines = attr.string_list(default = ["FOO"]),
        # do not inherit native.cc_library's `defines` attribute
        defines = None,
    },
    ...
)

इनहेरिट किए गए ऐसे एट्रिब्यूट की डिफ़ॉल्ट वैल्यू को हमेशा None पर सेट किया जाता है जो ज़रूरी नहीं हैं. भले ही, एट्रिब्यूट की मूल परिभाषा की डिफ़ॉल्ट वैल्यू कुछ भी हो. अगर आपको इनहेरिट किए गए किसी ऐसे एट्रिब्यूट की जांच करनी है या उसमें बदलाव करना है जो ज़रूरी नहीं है, तो आपको अपने मैक्रो के लागू करने वाले फ़ंक्शन में None केस को मैनेज करना होगा. उदाहरण के लिए, अगर आपको इनहेरिट किए गए tags एट्रिब्यूट में कोई टैग जोड़ना है, तो:

# macro/macro.bzl
_my_macro_implementation(name, visibility, tags, **kwargs):
    # Append a tag; tags attr is an inherited non-mandatory attribute, and
    # therefore is None unless explicitly set by the caller of our macro.
    my_tags = (tags or []) + ["another_tag"]
    native.cc_library(
        ...
        tags = my_tags,
        **kwargs,
    )
    ...

लागू करना

implementation, ऐसे फ़ंक्शन को स्वीकार करता है जिसमें मैक्रो का लॉजिक शामिल होता है. लागू करने वाले फ़ंक्शन, अक्सर एक या उससे ज़्यादा नियमों को कॉल करके टारगेट बनाते हैं. आम तौर पर, ये निजी होते हैं (इनके नाम में अंडरस्कोर होता है). आम तौर पर, इनका नाम मैक्रो के नाम जैसा ही होता है. हालांकि, इनके नाम के आगे _ और आखिर में _impl होता है.

नियम लागू करने वाले फ़ंक्शन, एक आर्ग्युमेंट (ctx) लेते हैं. इसमें एट्रिब्यूट का रेफ़रंस होता है. वहीं, मैक्रो लागू करने वाले फ़ंक्शन, हर आर्ग्युमेंट के लिए एक पैरामीटर लेते हैं.

# macro/macro.bzl
def _my_macro_impl(name, visibility, deps, create_test):
    cc_library(
        name = name + "_cc_lib",
        deps = deps,
    )

    if create_test:
        cc_test(
            name = name + "_test",
            srcs = ["my_test.cc"],
            deps = deps,
        )

अगर किसी मैक्रो को एट्रिब्यूट इनहेरिट किए जाते हैं, तो उसके लागू करने वाले फ़ंक्शन में **kwargs अवशेष कीवर्ड पैरामीटर होना चाहिए. इसे उस कॉल पर फ़ॉरवर्ड किया जा सकता है जो इनहेरिट किए गए नियम या सबमैक्रो को लागू करता है. (इससे यह पक्का करने में मदद मिलती है कि जिस नियम या मैक्रो से इनहेरिट किया जा रहा है, अगर वह कोई नया एट्रिब्यूट जोड़ता है, तो आपका मैक्रो काम करना बंद नहीं करेगा.)

एलान

मैक्रो का एलान करने के लिए, BUILD फ़ाइल में उनकी परिभाषा को लोड करके उसे कॉल किया जाता है.


# pkg/BUILD

my_macro(
    name = "macro_instance",
    deps = ["src.cc"] + select(
        {
            "//config_setting:special": ["special_source.cc"],
            "//conditions:default": [],
        },
    ),
    create_tests = True,
)

इससे टारगेट //pkg:macro_instance_cc_lib और//pkg:macro_instance_test बन जाएंगे.

नियम कॉल की तरह ही, अगर मैक्रो कॉल में किसी एट्रिब्यूट की वैल्यू None पर सेट है, तो उस एट्रिब्यूट को ऐसे माना जाता है जैसे मैक्रो कॉलर ने उसे छोड़ा हो. उदाहरण के लिए, ये दोनों मैक्रो कॉल एक जैसे हैं:

# pkg/BUILD
my_macro(name = "abc", srcs = ["src.cc"], deps = None)
my_macro(name = "abc", srcs = ["src.cc"])

आम तौर पर, यह BUILD फ़ाइलों में काम का नहीं होता. हालांकि, प्रोग्राम के हिसाब से किसी मैक्रो को किसी दूसरे मैक्रो में रैप करने पर, यह मददगार होता है.

विवरण

बनाए गए टारगेट के लिए नेमिंग कन्वेंशन

सिंबल वाले मैक्रो से बनाए गए किसी भी टारगेट या सब-मैक्रो के नाम, मैक्रो के name पैरामीटर से मैच होने चाहिए. इसके अलावा, इन नामों के आगे name और उसके बाद _ (इसका सुझाव दिया जाता है), . या - होना चाहिए. उदाहरण के लिए, my_macro(name = "foo") सिर्फ़ foo नाम वाली फ़ाइलें या टारगेट बना सकता है. इसके अलावा, foo_, foo- या foo. से शुरू होने वाली फ़ाइलें या टारगेट भी बना सकता है. जैसे, foo_bar.

मैक्रो के नाम रखने के कन्वेंशन का उल्लंघन करने वाले टारगेट या फ़ाइलों का एलान किया जा सकता है. हालांकि, इन्हें बिल्ट नहीं किया जा सकता और न ही इनका इस्तेमाल डिपेंडेंसी के तौर पर किया जा सकता है.

मैक्रो इंस्टेंस के तौर पर एक ही पैकेज में मौजूद, मैक्रो फ़ाइलों और टारगेट के नाम, मैक्रो टारगेट के संभावित नामों से अलग होने चाहिए. हालांकि, यह ज़रूरी नहीं है कि ऐसा ही हो. हम सिंबल मैक्रो की परफ़ॉर्मेंस को बेहतर बनाने के लिए, लेज़ी इवैल्यूएशन लागू करने की प्रोसेस में हैं. हालांकि, नाम देने के स्कीमा का उल्लंघन करने वाले पैकेज में, इस सुविधा का इस्तेमाल नहीं किया जा सकेगा.

पाबंदियां

लेगसी मैक्रो की तुलना में, सिंबल मैक्रो पर कुछ और पाबंदियां हैं.

सिम्बॉलिक मैक्रो

  • name आर्ग्युमेंट और visibility आर्ग्युमेंट लेना चाहिए
  • implementation फ़ंक्शन होना चाहिए
  • वैल्यू नहीं दिखा सकता
  • अपने आर्ग्युमेंट में बदलाव नहीं कर सकते
  • native.existing_rules() को तब तक कॉल नहीं कर सकते, जब तक वे खास finalizer मैक्रो न हों
  • native.package() को कॉल नहीं कर सकता
  • glob() को कॉल नहीं कर सकता
  • native.environment_group() को कॉल नहीं कर सकता
  • ऐसे टारगेट बनाने चाहिए जिनके नाम, नाम रखने के स्कीमा के मुताबिक हों
  • उन इनपुट फ़ाइलों का रेफ़रंस नहीं दिया जा सकता जिन्हें एर्ग्युमेंट के तौर पर एलान नहीं किया गया है या पास नहीं किया गया है. ज़्यादा जानकारी के लिए, प्रॉपर्टी के दिखने की सेटिंग और मैक्रो देखें.

विज़िबिलिटी और मैक्रो

Bazel में, दिखने की सेटिंग के बारे में ज़्यादा जानने के लिए, दिखना देखें.

टारगेट किसको दिखे

डिफ़ॉल्ट रूप से, सिंबल वाले मैक्रो से बनाए गए टारगेट सिर्फ़ उस पैकेज में दिखते हैं जिसमें मैक्रो की जानकारी देने वाली .bzl फ़ाइल होती है. खास तौर पर, ये सिम्बोल मैक्रो को कॉल करने वाले को तब तक नहीं दिखते, जब तक कॉलर, मैक्रो की .bzl फ़ाइल वाले पैकेज में न हो.

सिंबल मैक्रो को कॉल करने वाले को टारगेट दिखाने के लिए, नियम या इनर मैक्रो में visibility = visibility पास करें. टारगेट को ज़्यादा लोगों (या सार्वजनिक तौर पर) के लिए उपलब्ध कराकर, उसे अन्य पैकेज में भी दिखाया जा सकता है.

package() में बताई गई पैकेज की डिफ़ॉल्ट विज़िबिलिटी, डिफ़ॉल्ट रूप से सबसे बाहरी मैक्रो के visibility पैरामीटर पर पास की जाती है. हालांकि, यह मैक्रो पर निर्भर करता है कि वह visibility को अपने इंस्टैंशिएट किए गए टारगेट पर पास करे या नहीं!

डिपेंडेंसी विज़िबिलिटी

मैक्रो के लागू होने के दौरान जिन टारगेट का रेफ़रंस दिया जाता है वे उस मैक्रो की परिभाषा में दिखने चाहिए. प्रॉडक्ट को इनमें से किसी एक तरीके से दिखाया जा सकता है:

  • टारगेट, मैक्रो को तब दिखते हैं, जब उन्हें मैक्रो में लेबल, लेबल की सूची या लेबल-की या -वैल्यू वाले डायक्शन एट्रिब्यूट के ज़रिए भेजा जाता है. ऐसा, साफ़ तौर पर इनमें से किसी एक तरीके से किया जा सकता है:

# pkg/BUILD
my_macro(... deps = ["//other_package:my_tool"] )
  • ... या एट्रिब्यूट की डिफ़ॉल्ट वैल्यू के तौर पर:
# my_macro:macro.bzl
my_macro = macro(
  attrs = {"deps" : attr.label_list(default = ["//other_package:my_tool"])},
  ...
)
  • टारगेट, मैक्रो को तब भी दिखते हैं, जब उन्हें मैक्रो की जानकारी देने वाली .bzl फ़ाइल वाले पैकेज के लिए दिखने के तौर पर दिखाया गया हो:
# other_package/BUILD
# Any macro defined in a .bzl file in //my_macro package can use this tool.
cc_binary(
    name = "my_tool",
    visibility = "//my_macro:\\__pkg__",
)

चुनता है

अगर कोई एट्रिब्यूट configurable (डिफ़ॉल्ट) है और उसकी वैल्यू None नहीं है, तो मैक्रो लागू करने वाले फ़ंक्शन को एट्रिब्यूट की वैल्यू, सामान्य select में रैप की गई के तौर पर दिखेगी. इससे मैक्रो के लेखक को उन गड़बड़ियों का पता लगाना आसान हो जाता है जहां उन्हें उम्मीद नहीं थी कि एट्रिब्यूट की वैल्यू select हो सकती है.

उदाहरण के लिए, नीचे दिया गया मैक्रो देखें:

my_macro = macro(
    attrs = {"deps": attr.label_list()},  # configurable unless specified otherwise
    implementation = _my_macro_impl,
)

अगर my_macro को deps = ["//a"] के साथ शुरू किया जाता है, तो _my_macro_impl को deps पैरामीटर को select({"//conditions:default": ["//a"]}) पर सेट करके शुरू किया जाएगा. अगर इस वजह से लागू करने का फ़ंक्शन काम नहीं करता है (उदाहरण के लिए, कोड ने deps[0] की तरह वैल्यू को इंडेक्स करने की कोशिश की, जो select के लिए अनुमति नहीं है), तो मैक्रो बनाने वाला व्यक्ति इनमें से कोई एक विकल्प चुन सकता है: वह अपने मैक्रो को फिर से लिख सकता है, ताकि सिर्फ़ select के साथ काम करने वाले ऑपरेशन का इस्तेमाल किया जा सके या वह एट्रिब्यूट को कॉन्फ़िगर न किए जा सकने वाले (attr.label_list(configurable = False)) के तौर पर मार्क कर सकता है.select

नियम के टारगेट, इस बदलाव को उलट देते हैं और सामान्य select को बिना शर्त वाली वैल्यू के तौर पर सेव करते हैं. ऊपर दिए गए उदाहरण में, अगर _my_macro_impl किसी नियम के टारगेट my_rule(..., deps = deps) का एलान करता है, तो उस नियम के टारगेट का deps, ["//a"] के तौर पर सेव किया जाएगा. इससे यह पक्का होता है कि select-रैपिंग की वजह से, मैक्रो से इंस्टैंशिएट किए गए सभी टारगेट में, सामान्य select वैल्यू सेव न हों.

अगर कॉन्फ़िगर किए जा सकने वाले एट्रिब्यूट की वैल्यू None है, तो उसे select में रैप नहीं किया जाता. इससे यह पक्का होता है कि my_attr == None जैसे टेस्ट अब भी काम करते हैं. साथ ही, जब एट्रिब्यूट को कैलकुलेट किए गए डिफ़ॉल्ट वैल्यू वाले नियम पर फ़ॉरवर्ड किया जाता है, तो नियम ठीक से काम करता है. इसका मतलब है कि एट्रिब्यूट को बिलकुल भी पास नहीं किया गया है. किसी एट्रिब्यूट के लिए, None वैल्यू का इस्तेमाल करना हमेशा संभव नहीं होता. हालांकि, attr.label() टाइप के लिए और इनहेरिट किए गए ऐसे एट्रिब्यूट के लिए ऐसा किया जा सकता है जो ज़रूरी नहीं है.

फ़ाइनल करने वाले

नियम फ़ाइनलाइज़र एक खास सिंबल मैक्रो है. BUILD फ़ाइल में, इसके लेक्सिकल क्रम से कोई फ़र्क़ नहीं पड़ता. पैकेज लोड करने के आखिरी चरण में, सभी नॉन-फ़ाइनलाइज़र टारगेट तय होने के बाद, इसका आकलन किया जाता है. सामान्य सिंबल मैक्रो के मुकाबले, फ़ाइनलाइज़र native.existing_rules() को कॉल कर सकता है. यहां यह लेगसी मैक्रो से थोड़ा अलग तरीके से काम करता है: यह सिर्फ़ उन टारगेट का सेट दिखाता है जो फ़ाइनलाइज़र नियम के दायरे में नहीं आते. फ़ाइनलाइज़र, उस सेट की स्थिति पर दावा कर सकता है या नए टारगेट तय कर सकता है.

फ़ाइनलाइज़र का एलान करने के लिए, finalizer = True के साथ macro() को कॉल करें:

def _my_finalizer_impl(name, visibility, tags_filter):
    for r in native.existing_rules().values():
        for tag in r.get("tags", []):
            if tag in tags_filter:
                my_test(
                    name = name + "_" + r["name"] + "_finalizer_test",
                    deps = [r["name"]],
                    data = r["srcs"],
                    ...
                )
                continue

my_finalizer = macro(
    attrs = {"tags_filter": attr.string_list(configurable = False)},
    implementation = _impl,
    finalizer = True,
)

आलस

अहम जानकारी: हम धीरे-धीरे मैक्रो एक्सपैंशन और उसका आकलन करने की सुविधा लागू कर रहे हैं. यह सुविधा फ़िलहाल उपलब्ध नहीं है.

फ़िलहाल, BUILD फ़ाइल लोड होने के साथ ही सभी मैक्रो का आकलन किया जाता है. इससे उन पैकेज के टारगेट की परफ़ॉर्मेंस पर बुरा असर पड़ सकता है जिनमें काम के नहीं, बल्कि महंगे मैक्रो भी शामिल होते हैं. आने वाले समय में, नॉन-फ़ाइनलाइज़र सिंबल मैक्रो का आकलन सिर्फ़ तब किया जाएगा, जब वे बिल्ड के लिए ज़रूरी हों. प्रीफ़िक्स नेमिंग स्कीमा की मदद से, Bazel यह तय करता है कि अनुरोध किए गए टारगेट के हिसाब से, किस मैक्रो को बड़ा करना है.

माइग्रेशन से जुड़ी समस्या हल करना

माइग्रेशन से जुड़ी कुछ आम समस्याएं और उन्हें ठीक करने का तरीका यहां बताया गया है.

  • लेगसी मैक्रो कॉल glob()

glob() कॉल को अपनी BUILD फ़ाइल (या BUILD फ़ाइल से कॉल किए गए किसी लेगसी मैक्रो) में ले जाएं और label-list एट्रिब्यूट का इस्तेमाल करके, glob() वैल्यू को सिंबल मैक्रो में पास करें:

# BUILD file
my_macro(
    ...,
    deps = glob(...),
)
  • लेगसी मैक्रो में एक पैरामीटर है, जो starlark attr टाइप का मान्य नहीं है.

नेस्ट किए गए सिंबल मैक्रो में ज़्यादा से ज़्यादा लॉजिक शामिल करें, लेकिन टॉप लेवल मैक्रो को लेगसी मैक्रो बनाए रखें.

  • लेगसी मैक्रो, ऐसे नियम को कॉल करता है जो नाम देने के स्कीमा का उल्लंघन करने वाला टारगेट बनाता है

कोई बात नहीं, बस "आपत्तिजनक" टारगेट पर निर्भर न रहें. नाम की जांच को अनदेखा कर दिया जाएगा.