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

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

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

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

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

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

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

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

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

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

  2. GitHub से, C++ प्रोजेक्ट का उदाहरण डाउनलोड करें और इसे अपनी लोकल मशीन पर किसी खाली डायरेक्ट्री में रखें.

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

    cc_binary(
        name = "hello-world",
        srcs = ["hello-world.cc"],
    )
    
  4. --config फ़्लैग का इस्तेमाल करने के लिए, वर्कस्पेस डायरेक्ट्री के रूट में .bazelrc फ़ाइल बनाएं. इसमें यह कॉन्टेंट शामिल करें:

    # Use our custom-configured c++ toolchain.
    
    build:clang_config --crosstool_top=//toolchain:clang_suite
    
    # Use --cpu as a differentiator.
    
    build:clang_config --cpu=k8
    
    # Use the default Bazel C++ toolchain to build the tools used during the
    # build.
    
    build:clang_config --host_crosstool_top=@bazel_tools//tools/cpp:toolchain
    

build:{config_name} --flag=value एंट्री के लिए, कमांड लाइन फ़्लैग --config={config_name} को उस खास फ़्लैग से जोड़ा जाता है. इस्तेमाल किए गए फ़्लैग के बारे में जानने के लिए, यह दस्तावेज़ देखें: crosstool_top, cpu और host_crosstool_top.

जब आप अपना टारगेट bazel build --config=clang_config //main:hello-world की मदद से बनाते हैं, तो Bazel, cc_toolchain_suite //toolchain:clang_suite से आपके कस्टम टूलचेन का इस्तेमाल करता है. सूट में अलग-अलग सीपीयू के लिए अलग-अलग टूलचेन की सूची हो सकती है, इसलिए, इसे --cpu=k8 फ़्लैग से अलग किया जाता है.

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

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

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

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

    bazel build --config=clang_config //main:hello-world
    

    .bazelrc फ़ाइल में --crosstool_top=//toolchain:clang_suite तय करने की वजह से, Bazel यह गड़बड़ी दिखाता है:

    No such package `toolchain`: BUILD file not found on package path.
    

    वर्कस्पेस डायरेक्ट्री में, पैकेज के लिए toolchain डायरेक्ट्री बनाएं. साथ ही, toolchain डायरेक्ट्री में एक खाली BUILD फ़ाइल बनाएं.

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

    No such target '//toolchain:clang_suite': target 'clang_suite' not declared
    in package 'toolchain' defined by .../toolchain/BUILD
    

    toolchain/BUILD फ़ाइल में, इस तरह एक खाली फ़ाइल ग्रुप तय करें:

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

    '//toolchain:clang_suite' does not have mandatory providers: 'ToolchainInfo'
    

    Bazel को पता चला कि --crosstool_top फ़्लैग, ऐसे नियम की ओर इशारा करता है जो ज़रूरी ToolchainInfo प्रोवाइडर उपलब्ध नहीं कराता. इसलिए, आपको --crosstool_top को ऐसे नियम की ओर इशारा करना होगा जो ToolchainInfo उपलब्ध कराता है. यह cc_toolchain_suite नियम है. toolchain/BUILD फ़ाइल में, खाली फ़ाइल ग्रुप को इससे बदलें:

    cc_toolchain_suite(
        name = "clang_suite",
        toolchains = {
            "k8": ":k8_toolchain",
        },
    )
    

    toolchains एट्रिब्यूट, --cpu (और --compiler की वैल्यू भी, अगर तय की गई हो) की वैल्यू को cc_toolchain पर अपने-आप मैप करता है. आपने अब तक कोई भी cc_toolchain टारगेट तय नहीं किया है. Bazel जल्द ही इसकी शिकायत करेगा.

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

    Rule '//toolchain:k8_toolchain' does not exist
    

    अब आपको cc_toolchain टारगेट तय करने होंगे, जो cc_toolchain_suite.toolchains एट्रिब्यूट में मौजूद हर वैल्यू के लिए हैं. toolchain/BUILD फ़ाइल में यह जोड़ें:

    filegroup(name = "empty")
    
    cc_toolchain(
        name = "k8_toolchain",
        toolchain_identifier = "k8-toolchain",
        toolchain_config = ":k8_toolchain_config",
        all_files = ":empty",
        compiler_files = ":empty",
        dwp_files = ":empty",
        linker_files = ":empty",
        objcopy_files = ":empty",
        strip_files = ":empty",
        supports_param_files = 0,
    )
    
  5. बिल्ड को फिर से चलाएं. Bazel यह गड़बड़ी दिखाता है:

    Rule '//toolchain:k8_toolchain_config' does not exist
    

    इसके बाद, toolchain/BUILD फ़ाइल में ":k8_toolchain_config" टारगेट जोड़ें:

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

    '//toolchain:k8_toolchain_config' does not have mandatory providers:
    'CcToolchainConfigInfo'
    

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

    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")
    

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

    cc_toolchain_config(name = "k8_toolchain_config")
    
  7. बिल्ड को फिर से चलाएं. 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 नियम के लागू करने के तरीके में बदलाव करें. इसके लिए, आपको @bazel_tools//tools/cpp:cc_toolchain_config_lib.bzl से tool_path() कंस्ट्रक्टर की ज़रूरत होगी:

    # 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, आपके सिस्टम के लिए सही पाथ हों.

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

     ..../BUILD:3:1: undeclared inclusion(s) in rule '//main:hello-world':
     this rule is missing dependency declarations for the following files included by 'main/hello-world.cc':
     '/usr/include/c++/9/ctime'
     '/usr/include/x86_64-linux-gnu/c++/9/bits/c++config.h'
     '/usr/include/x86_64-linux-gnu/c++/9/bits/os_defines.h'
     ....
    

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

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

     return cc_common.create_cc_toolchain_config_info(
          ctx = ctx,
          cxx_builtin_include_directories = [ # NEW
            "/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,
     )
    
  9. बिल्ड कमांड को फिर से चलाएं. आपको इस तरह की गड़बड़ी दिखेगी:

    /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 एट्रिब्यूट का इस्तेमाल करना. यहां इसे इस तरह ठीक किया गया है कि टूलचेन का इस्तेमाल करने वाले किसी भी टारगेट को यह फ़्लैग तय न करना पड़े.

    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",
          "flag_group",
          "flag_set",
          "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],
      )
    
  10. bazel build --config=clang_config //main:hello-world चलाने पर, यह आखिर में बिल्ड हो जाना चाहिए.

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

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

अहम जानकारी: - आपको कमांड लाइन में --crosstool_top फ़्लैग तय करना होगा. यह cc_toolchain_suite की ओर इशारा करना चाहिए - .bazelrc फ़ाइल का इस्तेमाल करके, किसी खास कॉन्फ़िगरेशन के लिए शॉर्टकट बनाया जा सकता है - cc_toolchain_suite में, अलग-अलग सीपीयू और कंपाइलर के लिए cc_toolchains की सूची हो सकती है. इन्हें अलग करने के लिए, --cpu जैसे कमांड लाइन फ़्लैग का इस्तेमाल किया जा सकता है. - आपको टूलचेन को यह बताना होगा कि टूल कहां मौजूद हैं. इस ट्यूटोरियल में, एक आसान वर्शन दिया गया है. इसमें सिस्टम से टूल ऐक्सेस किए जाते हैं. अगर आपको ज़्यादा सेल्फ-कंटेन्ड अप्रोच में दिलचस्पी है, तो यहां वर्कस्पेस के बारे में पढ़ें. आपके टूल, किसी दूसरे वर्कस्पेस से आ सकते हैं. ऐसे में, आपको उनके फ़ाइलें, cc_toolchain के लिए उपलब्ध करानी होंगी. इसके लिए, एट्रिब्यूट पर टारगेट डिपेंडेंसी का इस्तेमाल करना होगा. जैसे, compiler_files. tool_paths में भी बदलाव करना होगा. - अलग-अलग कार्रवाइयों के लिए, कौनसे फ़्लैग पास किए जाने चाहिए, यह तय करने के लिए सुविधाएं बनाई जा सकती हैं. ये कार्रवाइयां, लिंकिंग या किसी अन्य तरह की कार्रवाई हो सकती हैं.

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

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