Bazel ट्यूटोरियल: C++ टूलचेन कॉन्फ़िगर करें

इस ट्यूटोरियल में, किसी प्रोजेक्ट के लिए C++ टूलचेन को कॉन्फ़िगर करने का तरीका बताने के लिए, एक उदाहरण दिया गया है.

आपको क्या सीखने को मिलेगा

इस ट्यूटोरियल में, आपको ये चीज़ें सीखने को मिलेंगी:

  • बिल्ड एनवायरमेंट सेट अप करना
  • टूलचेन रिज़ॉल्यूशन को डीबग करने के लिए, --toolchain_resolution_debug का इस्तेमाल करना
  • C++ टूलचेन को कॉन्फ़िगर करना
  • एक Starlark नियम बनाना, जो cc_toolchain के लिए अतिरिक्त कॉन्फ़िगरेशन उपलब्ध कराता है, ताकि Bazel clang की मदद से ऐप्लिकेशन बना सके
  • Linux मशीन पर bazel build //main:hello-world चलाकर, C++ बाइनरी बनाना
  • bazel build //main:hello-world --platforms=//:android_x86_64 चलाकर, Android के लिए बाइनरी को क्रॉस-कंपाइल करना

शुरू करने से पहले

इस ट्यूटोरियल में यह मान लिया गया है कि आप Linux का इस्तेमाल कर रहे हैं और आपने C++ ऐप्लिकेशन को सफलतापूर्वक बनाया है. साथ ही, आपने सही टूलिंग और लाइब्रेरी इंस्टॉल की हैं. इस ट्यूटोरियल में clang version 16 का इस्तेमाल किया गया है, जिसे अपने सिस्टम पर इंस्टॉल किया जा सकता है.

बिल्ड एनवायरमेंट सेट अप करना

अपना बिल्ड एनवायरमेंट इस तरह सेट अप करें:

  1. अगर आपने अब तक ऐसा नहीं किया है, तो Bazel 7.0.2 या इसके बाद का वर्शन डाउनलोड और इंस्टॉल करें.

  2. रूट फ़ोल्डर में, खाली MODULE.bazel फ़ाइल जोड़ें.

  3. main/BUILD फ़ाइल में, यह cc_binary टारगेट जोड़ें:

    cc_binary(
        name = "hello-world",
        srcs = ["hello-world.cc"],
    )
    

    Bazel, बिल्ड के दौरान C++ में लिखे गए कई इंटरनल टूल का इस्तेमाल करता है. जैसे, process-wrapper. इसलिए, होस्ट प्लैटफ़ॉर्म के लिए पहले से मौजूद डिफ़ॉल्ट C++ टूलचेन तय किया जाता है. इससे ये इंटरनल टूल, इस ट्यूटोरियल में बनाए गए टूलचेन का इस्तेमाल करके बिल्ड कर पाते हैं. इसलिए, cc_binary टारगेट को भी डिफ़ॉल्ट टूलचेन के साथ बनाया जाता है.

  4. बिल्ड को इस कमांड से चलाएं:

    bazel build //main:hello-world
    

    MODULE.bazel में कोई टूलचेन रजिस्टर न होने पर भी, बिल्ड पूरा हो जाता है.

    ज़्यादा जानकारी के लिए, यह कमांड चलाएं:

    bazel build //main:hello-world --toolchain_resolution_debug='@bazel_tools//tools/cpp:toolchain_type'
    
    INFO: ToolchainResolution: Target platform @@platforms//host:host: Selected execution platform @@platforms//host:host, type @@bazel_tools//tools/cpp:toolchain_type -> toolchain @@bazel_tools+cc_configure_extension+local_config_cc//:cc-compiler-k8
    

    --platforms तय किए बिना, Bazel @platforms//host के लिए टारगेट को @bazel_tools+cc_configure_extension+local_config_cc//:cc-compiler-k8 का इस्तेमाल करके बनाता है.

C++ टूलचेन को कॉन्फ़िगर करना

C++ टूलचेन को कॉन्फ़िगर करने के लिए, ऐप्लिकेशन को बार-बार बनाएं और एक-एक करके हर गड़बड़ी को ठीक करें. इसके लिए, यहां दिया गया तरीका अपनाएं.

इसमें यह भी मान लिया गया है कि clang version 9.0.1 का इस्तेमाल किया जा रहा है. हालांकि, clang के अलग-अलग वर्शन के बीच, जानकारी में मामूली बदलाव ही होता है.

  1. toolchain/BUILD में यह कोड जोड़ें:

    filegroup(name = "empty")
    
    cc_toolchain(
        name = "linux_x86_64_toolchain",
        toolchain_identifier = "linux_x86_64-toolchain",
        toolchain_config = ":linux_x86_64_toolchain_config",
        all_files = ":empty",
        compiler_files = ":empty",
        dwp_files = ":empty",
        linker_files = ":empty",
        objcopy_files = ":empty",
        strip_files = ":empty",
        supports_param_files = 0,
    )
    
    toolchain(
        name = "cc_toolchain_for_linux_x86_64",
        toolchain = ":linux_x86_64_toolchain",
        toolchain_type = "@bazel_tools//tools/cpp:toolchain_type",
        exec_compatible_with = [
            "@platforms//cpu:x86_64",
            "@platforms//os:linux",
        ],
        target_compatible_with = [
            "@platforms//cpu:x86_64",
            "@platforms//os:linux",
        ],
    )
    

    इसके बाद, सही डिपेंडेंसी जोड़ें और MODULE.bazel में इस कोड की मदद से टूलचेन रजिस्टर करें:

    bazel_dep(name = "platforms", version = "0.0.10")
    register_toolchains(
        "//toolchain:cc_toolchain_for_linux_x86_64"
    )
    

    इस चरण में, cc_toolchain तय किया जाता है और इसे होस्ट कॉन्फ़िगरेशन के लिए toolchain टारगेट से जोड़ा जाता है.

  2. बिल्ड को फिर से चलाएं. toolchain पैकेज में अब तक linux_x86_64_toolchain_config टारगेट तय नहीं किया गया है. इसलिए, Bazel यह गड़बड़ी दिखाता है:

    ERROR: toolchain/BUILD:4:13: in toolchain_config attribute of cc_toolchain rule //toolchain:linux_x86_64_toolchain: rule '//toolchain:linux_x86_64_toolchain_config' does not exist.
    
  3. toolchain/BUILD फ़ाइल में, खाली फ़ाइल ग्रुप को इस तरह तय करें:

    package(default_visibility = ["//visibility:public"])
    
    filegroup(name = "linux_x86_64_toolchain_config")
    
  4. बिल्ड को फिर से चलाएं. Bazel यह गड़बड़ी दिखाता है:

    '//toolchain:linux_x86_64_toolchain_config' does not have mandatory providers: 'CcToolchainConfigInfo'.
    

    CcToolchainConfigInfo एक ऐसा प्रोवाइडर है जिसका इस्तेमाल, C++ टूलचेन को कॉन्फ़िगर करने के लिए किया जाता है. इस गड़बड़ी को ठीक करने के लिए, एक Starlark नियम बनाएं. यह नियम, toolchain/cc_toolchain_config.bzl फ़ाइल बनाकर, Bazel को CcToolchainConfigInfo उपलब्ध कराता है. इस फ़ाइल में यह कॉन्टेंट शामिल करें:

    def _impl(ctx):
        return cc_common.create_cc_toolchain_config_info(
            ctx = ctx,
            toolchain_identifier = "k8-toolchain",
            host_system_name = "local",
            target_system_name = "local",
            target_cpu = "k8",
            target_libc = "unknown",
            compiler = "clang",
            abi_version = "unknown",
            abi_libc_version = "unknown",
        )
    
    cc_toolchain_config = rule(
        implementation = _impl,
        attrs = {},
        provides = [CcToolchainConfigInfo],
    )
    

    cc_common.create_cc_toolchain_config_info() ज़रूरी प्रोवाइडर CcToolchainConfigInfo बनाता है. cc_toolchain_config नियम का इस्तेमाल करने के लिए, toolchain/BUILD में पैकेज स्टेटमेंट के ठीक नीचे, लोड स्टेटमेंट जोड़ें:

    load(":cc_toolchain_config.bzl", "cc_toolchain_config")
    

    साथ ही, "linux_x86_64_toolchain_config" फ़ाइल ग्रुप को, cc_toolchain_config नियम के एलान से बदलें:

    cc_toolchain_config(name = "linux_x86_64_toolchain_config")
    
  5. बिल्ड को फिर से चलाएं. Bazel यह गड़बड़ी दिखाता है:

    .../BUILD:1:1: C++ compilation of rule '//:hello-world' failed (Exit 1)
    src/main/tools/linux-sandbox-pid1.cc:421:
    "execvp(toolchain/DUMMY_GCC_TOOL, 0x11f20e0)": No such file or directory
    Target //:hello-world failed to build`
    

    इस समय, Bazel के पास कोड बनाने की कोशिश करने के लिए ज़रूरी जानकारी होती है. हालांकि, उसे अब भी यह नहीं पता होता कि ज़रूरी बिल्ड ऐक्शन पूरे करने के लिए किन टूल का इस्तेमाल करना है. Bazel को यह बताने के लिए कि किन टूल का इस्तेमाल करना है, Starlark नियम के लागू करने के तरीके में बदलाव करें. इसके लिए, आपको tool_path() कंस्ट्रक्टर की ज़रूरत होगी @bazel_tools//tools/cpp:cc_toolchain_config_lib.bzl:

    # toolchain/cc_toolchain_config.bzl:
    # NEW
    load("@bazel_tools//tools/cpp:cc_toolchain_config_lib.bzl", "tool_path")
    
    def _impl(ctx):
        tool_paths = [ # NEW
            tool_path(
                name = "gcc",
                path = "/usr/bin/clang",
            ),
            tool_path(
                name = "ld",
                path = "/usr/bin/ld",
            ),
            tool_path(
                name = "ar",
                path = "/usr/bin/ar",
            ),
            tool_path(
                name = "cpp",
                path = "/bin/false",
            ),
            tool_path(
                name = "gcov",
                path = "/bin/false",
            ),
            tool_path(
                name = "nm",
                path = "/bin/false",
            ),
            tool_path(
                name = "objdump",
                path = "/bin/false",
            ),
            tool_path(
                name = "strip",
                path = "/bin/false",
            ),
        ]
    
        return cc_common.create_cc_toolchain_config_info(
            ctx = ctx,
            toolchain_identifier = "local",
            host_system_name = "local",
            target_system_name = "local",
            target_cpu = "k8",
            target_libc = "unknown",
            compiler = "clang",
            abi_version = "unknown",
            abi_libc_version = "unknown",
            tool_paths = tool_paths, # NEW
        )
    

    पक्का करें कि /usr/bin/clang और /usr/bin/ld आपके सिस्टम के लिए सही पाथ हों.

  6. बिल्ड को फिर से चलाएं. Bazel यह गड़बड़ी दिखाता है:

    ERROR: main/BUILD:3:10: Compiling main/hello-world.cc failed: absolute path inclusion(s) found in rule '//main:hello-world':
    the source file 'main/hello-world.cc' includes the following non-builtin files with absolute paths (if these are builtin files, make sure these paths are in your toolchain):
      '/usr/include/c++/13/ctime'
      '/usr/include/x86_64-linux-gnu/c++/13/bits/c++config.h'
      '/usr/include/x86_64-linux-gnu/c++/13/bits/os_defines.h'
      ...
    

    Bazel को यह जानना ज़रूरी है कि शामिल किए गए हेडर कहां खोजने हैं. इसे ठीक करने के कई तरीके हैं. जैसे, cc_binary के includes एट्रिब्यूट का इस्तेमाल करना. हालांकि, यहां इसे टूलचेन लेवल पर ठीक किया गया है. इसके लिए, cxx_builtin_include_directories के cc_common.create_cc_toolchain_config_info पैरामीटर का इस्तेमाल किया गया है. ध्यान दें कि अगर clang का कोई दूसरा वर्शन इस्तेमाल किया जा रहा है, तो शामिल करने का पाथ अलग होगा. डिस्ट्रिब्यूशन के हिसाब से भी ये पाथ अलग हो सकते हैं.

    toolchain/cc_toolchain_config.bzl में, रिटर्न वैल्यू में इस तरह बदलाव करें:

    return cc_common.create_cc_toolchain_config_info(
        ctx = ctx,
        cxx_builtin_include_directories = [ # NEW
            "/usr/lib/llvm-16/lib/clang/16/include",
            "/usr/include",
        ],
        toolchain_identifier = "local",
        host_system_name = "local",
        target_system_name = "local",
        target_cpu = "k8",
        target_libc = "unknown",
        compiler = "clang",
        abi_version = "unknown",
        abi_libc_version = "unknown",
        tool_paths = tool_paths,
    )
    
  7. बिल्ड कमांड को फिर से चलाएं. आपको इस तरह की गड़बड़ी दिखेगी:

    /usr/bin/ld: bazel-out/k8-fastbuild/bin/main/_objs/hello-world/hello-world.o: in function `print_localtime()':
    hello-world.cc:(.text+0x68): undefined reference to `std::cout'
    

    इसकी वजह यह है कि लिंकर में C++ स्टैंडर्ड लाइब्रेरी मौजूद नहीं है और उसे इसके सिंबल नहीं मिल रहे हैं. इसे ठीक करने के कई तरीके हैं. जैसे, cc_binary के linkopts एट्रिब्यूट का इस्तेमाल करना. यहां इसे इस तरह ठीक किया गया है कि टूलचेन का इस्तेमाल करने वाले किसी भी टारगेट के लिए, इस फ़्लैग को तय न करना पड़े.

    toolchain/cc_toolchain_config.bzl में यह कोड कॉपी करें:

    # NEW
    load("@bazel_tools//tools/build_defs/cc:action_names.bzl", "ACTION_NAMES")
    # NEW
    load(
        "@bazel_tools//tools/cpp:cc_toolchain_config_lib.bzl",
        "feature",    # NEW
        "flag_group", # NEW
        "flag_set",   # NEW
        "tool_path",
    )
    
    all_link_actions = [ # NEW
        ACTION_NAMES.cpp_link_executable,
        ACTION_NAMES.cpp_link_dynamic_library,
        ACTION_NAMES.cpp_link_nodeps_dynamic_library,
    ]
    
    def _impl(ctx):
        tool_paths = [
            tool_path(
                name = "gcc",
                path = "/usr/bin/clang",
            ),
            tool_path(
                name = "ld",
                path = "/usr/bin/ld",
            ),
            tool_path(
                name = "ar",
                path = "/bin/false",
            ),
            tool_path(
                name = "cpp",
                path = "/bin/false",
            ),
            tool_path(
                name = "gcov",
                path = "/bin/false",
            ),
            tool_path(
                name = "nm",
                path = "/bin/false",
            ),
            tool_path(
                name = "objdump",
                path = "/bin/false",
            ),
            tool_path(
                name = "strip",
                path = "/bin/false",
            ),
        ]
    
        features = [ # NEW
            feature(
                name = "default_linker_flags",
                enabled = True,
                flag_sets = [
                    flag_set(
                        actions = all_link_actions,
                        flag_groups = ([
                            flag_group(
                                flags = [
                                    "-lstdc++",
                                ],
                            ),
                        ]),
                    ),
                ],
            ),
        ]
    
        return cc_common.create_cc_toolchain_config_info(
            ctx = ctx,
            features = features, # NEW
            cxx_builtin_include_directories = [
                "/usr/lib/llvm-9/lib/clang/9.0.1/include",
                "/usr/include",
            ],
            toolchain_identifier = "local",
            host_system_name = "local",
            target_system_name = "local",
            target_cpu = "k8",
            target_libc = "unknown",
            compiler = "clang",
            abi_version = "unknown",
            abi_libc_version = "unknown",
            tool_paths = tool_paths,
        )
    
    cc_toolchain_config = rule(
        implementation = _impl,
        attrs = {},
        provides = [CcToolchainConfigInfo],
    )
    
  8. bazel build //main:hello-world चलाने पर, इसे होस्ट के लिए बाइनरी को सफलतापूर्वक बनाना चाहिए.

  9. toolchain/BUILD में, cc_toolchain_config, cc_toolchain, और toolchain टारगेट कॉपी करें. साथ ही, टारगेट के नामों में linux_x86_64 को android_x86_64 से बदलें.

    MODULE.bazel में, Android के लिए टूलचेन रजिस्टर करें

    register_toolchains(
        "//toolchain:cc_toolchain_for_linux_x86_64",
        "//toolchain:cc_toolchain_for_android_x86_64"
    )
    
  10. Android के लिए बाइनरी बनाने के लिए, bazel build //main:hello-world --android_platforms=//toolchain:android_x86_64 चलाएं.

असल में, Linux और Android के लिए C++ टूलचेन कॉन्फ़िगरेशन अलग-अलग होने चाहिए. अंतर के लिए, मौजूदा cc_toolchain_config में बदलाव किया जा सकता है या अलग-अलग प्लैटफ़ॉर्म के लिए अलग-अलग नियम (यानी, CcToolchainConfigInfo प्रोवाइडर) बनाए जा सकते हैं.

अपने काम की समीक्षा करना

इस ट्यूटोरियल में, आपने बुनियादी C++ टूलचेन को कॉन्फ़िगर करने का तरीका सीखा. हालांकि, टूलचेन इस उदाहरण से ज़्यादा पावरफ़ुल होते हैं.

मुख्य बातें:

  • Bazel को प्लैटफ़ॉर्म पर एक जैसी कंस्ट्रेंट वैल्यू के लिए टूलचेन को रिज़ॉल्व करने के लिए, कमांड लाइन में मैच करने वाला platforms फ़्लैग तय करना होगा. भाषा के हिसाब से कॉन्फ़िगरेशन फ़्लैग के बारे में ज़्यादा जानकारी, दस्तावेज़ में दी गई है .
  • आपको टूलचेन को यह बताना होगा कि टूल कहां मौजूद हैं. इस ट्यूटोरियल में, एक आसान वर्शन दिया गया है. इसमें सिस्टम से टूल ऐक्सेस किए जाते हैं. अगर आपको ज़्यादा सेल्फ-कंटेन्ड अप्रोच चाहिए, तो बाहरी डिपेंडेंसी के बारे में पढ़ें. आपके टूल किसी दूसरे मॉड्यूल से आ सकते हैं. ऐसे में, आपको एट्रिब्यूट पर टारगेट डिपेंडेंसी के साथ, उनकी फ़ाइलें cc_toolchain के लिए उपलब्ध करानी होंगी. जैसे, compiler_files. tool_paths में भी बदलाव करना होगा.
  • अलग-अलग कार्रवाइयों के लिए, कौनसे फ़्लैग पास किए जाने चाहिए, यह तय करने के लिए सुविधाएं बनाई जा सकती हैं. ये कार्रवाइयां, लिंकिंग या किसी अन्य तरह की कार्रवाई हो सकती हैं.

इस बारे में और पढ़ें

ज़्यादा जानकारी के लिए, C++ टूलचेन कॉन्फ़िगरेशन देखें