البرنامج التعليمي Bazel: إعداد C++ Toolchains

يستخدِم هذا البرنامج التعليمي سيناريو نموذجيًا لوصف كيفية إعداد أدوات C++ لمشروع. ويستند ذلك إلى مثال على مشروع C++ والذي يخلو من الأخطاء باستخدام clang.

ما ستتعرَّف عليه

ستتعرّف في هذا البرنامج التعليمي على كيفية:

  • إعداد بيئة الإصدار
  • ضبط سلسلة أدوات C++
  • أنشِئ قاعدة Starlark التي تقدِّم ضبطًا إضافيًا لجهاز cc_toolchain حتى يتمكّن Bazel من إنشاء التطبيق باستخدام clang
  • يمكنك تأكيد النتيجة المتوقعة من خلال تشغيل bazel build --config=clang_config //main:hello-world على جهاز يعمل بنظام التشغيل Linux.
  • إنشاء تطبيق C++

قبل البدء

إعداد بيئة الإصدار

يفترض هذا البرنامج التعليمي أنك تستخدم نظام التشغيل Linux وقد أنشأت تطبيقات 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، يستخدم بازيل سلسلة الأدوات المخصّصة الخاصة بك من cc_toolchain_suite //toolchain:clang_suite. يمكن أن تدرج الحزمة سلاسل أدوات مختلفة لمختلف وحدات المعالجة المركزية، ولهذا السبب يتم تمييزها مع العلامة --cpu=k8.

لأن Bazel تستخدم العديد من الأدوات الداخلية المكتوبة باستخدام C++ أثناء عملية الإنشاء، مثل برنامج تضمين المعالجة، يتم تحديد سلسلة أدوات 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'
    

    اكتشف بازيل أن علامة --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
    

    بعد ذلك، أضِف استهداف ":k8_toolchain_config" إلى ملف toolchain/BUILD:

    filegroup(name = "k8_toolchain_config")
    
  6. شغِّل الإصدار مرة أخرى. يعرض Bazel الخطأ التالي:

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

    CcToolchainConfigInfo هو موفّر خدمة يمكنك استخدامه لإعداد سلاسل أدوات C++ الخاصة بك. لإصلاح هذا الخطأ، أنشئ قاعدة Starlark توفر CcToolchainConfigInfo لـ Bazel من خلال إنشاء ملف 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، أضِف بيان التحميل إلى 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`
    

    في هذه المرحلة، تتوفّر لدى "بازيل" معلومات كافية لمحاولة إنشاء الرمز، لكنها لا تزال لا تعرف الأدوات التي يمكن استخدامها لإكمال إجراءات الإصدار المطلوبة. عليك تعديل تنفيذ قاعدة Starlark لإعلام "بازيل" بالأدوات التي يمكن استخدامها. لتنفيذ هذا الإجراء، يجب استخدام أداة إنشاء bundle_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، سيكون مسار التضمين مختلفًا. وقد تختلف هذه المسارات أيضًا حسب التوزيع.

    عدِّل قيمة الإرجاع في 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 للتمييز بينها. - يتعيّن عليك السماح لسلسلة الأدوات بمعرفة أماكن الأدوات. في هذا البرنامج التعليمي، تتوفر نسخة مبسّطة يمكنك من خلالها الوصول إلى الأدوات التي يوفّرها النظام. إذا كنت مهتمًا باتّباع نهج مستقل أكثر، يمكنك الاطّلاع على مساحات العمل هنا. ويمكن أن تأتي أدواتك من مساحة عمل مختلفة، ما يجعل عليك إتاحة ملفاتها لـ cc_toolchain مع اعتمادية الارتباطات على السمات، مثل compiler_files. يجب أيضًا تغيير tool_paths. - يمكنك إنشاء ميزات لتخصيص العلامات التي يجب تمريرها إلى الإجراءات المختلفة، سواء كانت رابطًا أو أي نوع آخر من الإجراءات.

قراءة إضافية

لمزيد من التفاصيل، يمكنك الاطّلاع على ضبط سلسلة أدوات C++