آموزش بازل: زنجیره ابزار C++ را پیکربندی کنید

این آموزش از یک سناریوی مثال برای توصیف نحوه پیکربندی زنجیره‌های ابزار C++ برای یک پروژه استفاده می‌کند. این بر اساس یک پروژه مثال C ++ است که بدون خطا با استفاده از clang ایجاد می کند.

چیزی که یاد خواهید گرفت

در این آموزش شما یاد می گیرید که چگونه:

  • محیط ساخت را تنظیم کنید
  • زنجیره ابزار C++ را پیکربندی کنید
  • یک قانون Starlark ایجاد کنید که پیکربندی اضافی برای cc_toolchain فراهم می کند تا Bazel بتواند برنامه را با clang بسازد.
  • با اجرای bazel build --config=clang_config //main:hello-world در یک ماشین لینوکس، نتیجه مورد انتظار را تأیید کنید.
  • برنامه C++ را بسازید

قبل از اینکه شروع کنی

محیط ساخت را تنظیم کنید

این آموزش فرض می‌کند که شما روی لینوکس هستید و برنامه‌های C++ را با موفقیت ساخته‌اید و ابزارها و کتابخانه‌های مناسب را نصب کرده‌اید. در این آموزش از clang version 9.0.1 استفاده شده است که می توانید آن را روی سیستم خود نصب کنید.

محیط ساخت خود را به صورت زیر تنظیم کنید:

  1. اگر قبلا این کار را نکرده اید، Bazel 0.23 یا بالاتر را دانلود و نصب کنید .

  2. نمونه پروژه ++C را از GitHub دانلود کنید و آن را در یک پوشه خالی در ماشین محلی خود قرار دهید.

  3. هدف cc_binary زیر را به فایل main/BUILD اضافه کنید:

    cc_binary(
        name = "hello-world",
        srcs = ["hello-world.cc"],
    )
    
  4. یک فایل .bazelrc . در ریشه دایرکتوری فضای کاری با محتویات زیر ایجاد کنید تا استفاده از پرچم --config را فعال کنید:

    # 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 های مختلف فهرست کند، و به همین دلیل است که با پرچم --cpu=k8 می شود.

از آنجایی که Bazel از بسیاری از ابزارهای داخلی نوشته شده در C++ در حین ساخت استفاده می کند، مانند process-wrapper، زنجیره ابزار پیش فرض C++ موجود برای پلتفرم میزبان مشخص شده است، به طوری که این ابزارها به جای ابزار ایجاد شده در این آموزش، با استفاده از آن زنجیره ابزار ساخته می شوند. .

پیکربندی زنجیره ابزار C++

برای پیکربندی زنجیره ابزار C++، به طور مکرر برنامه را بسازید و هر خطا را یکی یکی حذف کنید، همانطور که در زیر توضیح داده شده است.

  1. بیلد را با دستور زیر اجرا کنید:

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

    از آنجایی که شما --crosstool_top=//toolchain:clang_suite را در فایل .bazelrc . مشخص کرده اید، Bazel خطای زیر را می دهد:

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

    در دایرکتوری فضای کاری، دایرکتوری toolchain برای بسته و یک فایل BUILD خالی در دایرکتوری toolchain ایجاد کنید.

  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 به زودی در مورد آن شکایت خواهد کرد.

  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
    

    سپس یک هدف ":k8_toolchain_config" را به فایل toolchain/BUILD اضافه کنید:

    filegroup(name = "k8_toolchain_config")
    
  6. بیلد را دوباره اجرا کنید. Bazel خطای زیر را می دهد:

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

    CcToolchainConfigInfo ارائه دهنده ای است که شما از آن برای پیکربندی زنجیره های ابزار C++ خود استفاده می کنید. برای رفع این خطا، یک قانون CcToolchainConfigInfo ایجاد کنید که با ایجاد یک فایل toolchain/cc_toolchain_config.bzl با محتوای زیر، CcToolchainConfigInfo را در اختیار Bazel قرار می دهد:

    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 ، یک دستور بار را به toolchains/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 اطلاعات کافی برای تلاش برای ساخت کد دارد، اما هنوز نمی داند از چه ابزارهایی برای تکمیل اقدامات ساخت مورد نیاز استفاده کند. شما اجرای قانون Starlark را تغییر خواهید داد تا به Bazel بگویید از چه ابزارهایی استفاده کند. برای آن، به سازنده 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 مسیرهای صحیح سیستم شما هستند.

  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 باید بداند که کجا سرصفحه های موجود را جستجو کند. راه‌های مختلفی برای حل این مشکل وجود دارد، مانند استفاده includes ویژگی cc_binary ، اما در اینجا این مشکل در سطح زنجیره ابزار با پارامتر cxx_builtin_include_directories cc_common.create_cc_toolchain_config_info . مراقب باشید که اگر از نسخه دیگری از clang استفاده می کنید، مسیر include متفاوت خواهد بود. این مسیرها نیز ممکن است بسته به توزیع متفاوت باشند.

    مقدار بازگشتی در 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. دوباره دستور build را اجرا کنید، با خطایی مانند:

    /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++ را ندارد و نمی تواند نمادهای آن را پیدا کند. راه های زیادی برای حل این مشکل وجود دارد، مانند استفاده از ویژگی linkopts cc_binary . در اینجا با اطمینان از اینکه هیچ هدفی که از زنجیره ابزار استفاده می کند نیازی به تعیین این پرچم ندارد، حل می شود.

    کد زیر را در 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 استفاده کنید. - باید به زنجیره ابزار اطلاع دهید که ابزارها در کجا زندگی می کنند. در این آموزش یک نسخه ساده شده وجود دارد که در آن به ابزارهای سیستم دسترسی دارید. اگر به یک رویکرد مستقل‌تر علاقه دارید، می‌توانید درباره فضاهای کاری اینجا بخوانید . ابزارهای شما می‌توانند از فضای کاری متفاوتی باشند و باید فایل‌های آن‌ها را با وابستگی‌های هدف به ویژگی‌هایی مانند compiler_files در دسترس cc_toolchain قرار دهید. tool_paths نیز باید تغییر کند. - می‌توانید ویژگی‌هایی را برای سفارشی کردن پرچم‌ها ایجاد کنید که باید به اقدامات مختلف منتقل شوند، خواه پیوند یا هر نوع عمل دیگری.

بیشتر خواندن

برای جزئیات بیشتر، به پیکربندی زنجیره ابزار C++ مراجعه کنید