मैक्रो

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

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

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

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

इस्तेमाल

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

विशेषताएं

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"}) बन जाएगा. ज़्यादा जानने के लिए, चुनें पर जाएं.

लागू करना

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

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

# macro/macro.bzl
def _my_macro_impl(name, 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,
        )

एलान

मैक्रो का एलान करने के लिए, 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 बन जाएंगे.

विवरण

बनाए गए टारगेट के नाम रखने के तरीके

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

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

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

पाबंदियां

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

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

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

किसको दिखे

TODO: इस सेक्शन को बड़ा करें

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

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

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

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

  • मैक्रो में साफ़ तौर पर attr वैल्यू के तौर पर पास की गई

# pkg/BUILD
my_macro(... deps = ["//other_package:my_tool"] )
  • attr वैल्यू का इंप्लिसिट डिफ़ॉल्ट
# my_macro:macro.bzl
my_macro = macro(
  attrs = {"deps" : attr.label_list(default = ["//other_package:my_tool"])} )
  • मैक्रो की परिभाषा को पहले से ही दिख रहा है
# other_package/BUILD
cc_binary(
    name = "my_tool",
    visibility = "//my_macro:\\__pkg__",
)

चुनता है

अगर कोई एट्रिब्यूट configurable है, तो मैक्रो लागू करने वाला फ़ंक्शन, एट्रिब्यूट की वैल्यू को हमेशा 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"]}) पर सेट किया गया है.

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

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

नियम फ़ाइनलाइज़र एक खास सिंबल मैक्रो है. 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(...),
)
  • लेगसी मैक्रो में एक ऐसा पैरामीटर है जो मान्य स्टारलार्क attr टाइप नहीं है.

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

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

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