Bazel Eğitimi: C++ Araç Zincirlerini Yapılandırma

Bu eğitimde, bir proje için C++ araç zincirlerinin nasıl yapılandırılacağı örnek bir senaryoyla açıklanmaktadır. Bu örnek, clang kullanılarak hatasız şekilde derlenen örnek bir C++ projesine dayanır.

Neler öğreneceksiniz?

Bu eğitimde şunları öğreneceksiniz:

  • Derleme ortamını ayarlama
  • C++ araç zincirini yapılandırma
  • Bazel'in uygulamayı clang ile derleyebilmesi için cc_toolchain için ek yapılandırma sağlayan bir Starlark kuralı oluşturun
  • bazel build --config=clang_config //main:hello-world dosyasını Linux makinesinde çalıştırarak beklenen sonucu onaylama
  • C++ uygulamasını derleme

Başlamadan önce

Derleme ortamını ayarlama

Bu eğiticide, Linux kullandığınız ve C++ uygulamalarını başarıyla derlediğiniz ve uygun araçlar ile kitaplıkları yüklediğiniz varsayılır. Eğitimde, sisteminize yükleyebileceğiniz clang version 9.0.1 kullanılır.

Derleme ortamınızı aşağıdaki gibi ayarlayın:

  1. Henüz yapmadıysanız Bazel 0.23 veya sonraki bir sürümü indirip yükleyin.

  2. GitHub'dan örnek C++ projesini indirip yerel makinenizdeki boş bir dizine yerleştirin.

  3. main/BUILD dosyasına aşağıdaki cc_binary hedefini ekleyin:

    cc_binary(
        name = "hello-world",
        srcs = ["hello-world.cc"],
    )
    
  4. --config işaretinin kullanımını etkinleştirmek için çalışma alanı dizininin kökünde aşağıdaki içerikleri içeren bir .bazelrc dosyası oluşturun:

    # 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 girişi için komut satırı işareti --config={config_name}, söz konusu işaretle ilişkilendirilir. Kullanılan işaretler için dokümanlara bakın: crosstool_top, cpu ve host_crosstool_top.

bazel build --config=clang_config //main:hello-world ile hedefinizi derlediğinizde Bazel, cc_toolchain_suite //toolchain:clang_suite öğesinden özel araç zincirinizi kullanır. Paket, farklı CPU'lar için farklı araç zincirleri listeleyebilir. Bu nedenle, --cpu=k8 işaretiyle diğerlerinden ayrılır.

Bazel, derleme sırasında C++'ta yazılan işlem sarmalayıcı gibi birçok dahili araç kullandığından, ana makine platformu için önceden mevcut olan varsayılan C++ araç zinciri belirtilmiştir. Böylece, bu araçlar bu eğiticide oluşturulan araç yerine o araç zinciri kullanılarak oluşturulur.

C++ araç zincirini yapılandırma

C++ araç zincirini yapılandırmak için uygulamayı tekrar tekrar derleyin ve aşağıda açıklandığı gibi her hatayı tek tek giderin.

  1. Aşağıdaki komutu kullanarak derlemeyi çalıştırın:

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

    .bazelrc dosyasında --crosstool_top=//toolchain:clang_suite belirttiğiniz için Bazel aşağıdaki hatayı verir:

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

    Çalışma alanı dizininde, paket için toolchain dizini ve toolchain dizininin içinde boş bir BUILD dosyası oluşturun.

  2. Derlemeyi tekrar çalıştırın. toolchain paketi henüz clang_suite hedefini tanımlamadığından Bazel aşağıdaki hatayı verir:

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

    toolchain/BUILD dosyasında boş bir dosya grubunu aşağıdaki gibi tanımlayın:

    package(default_visibility = ["//visibility:public"])
    
    filegroup(name = "clang_suite")
    
  3. Derlemeyi tekrar çalıştırın. Bazel aşağıdaki hatayı verir:

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

    Bazel, --crosstool_top işaretinin gerekli ToolchainInfo sağlayıcısını sağlamayan bir kuralı işaret ettiğini fark etti. Bu nedenle, --crosstool_topToolchainInfo sağlayan bir kurala (cc_toolchain_suite kuralı) yönlendirmeniz gerekir. toolchain/BUILD dosyasında boş filegroup öğesini aşağıdakiyle değiştirin:

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

    toolchains özelliği, --cpu (ve belirtilirse --compiler) değerlerini cc_toolchain ile otomatik olarak eşler. Henüz cc_toolchain hedefi tanımlamadınız ve Bazel kısa süre içinde bu konuda şikayette bulunacaktır.

  4. Derlemeyi tekrar çalıştırın. Bazel aşağıdaki hatayı verir:

    Rule '//toolchain:k8_toolchain' does not exist
    

    Şimdi cc_toolchain_suite.toolchains özelliğindeki her değer için cc_toolchain hedefleri tanımlamanız gerekiyor. toolchain/BUILD dosyasına aşağıdakileri ekleyin:

    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. Derlemeyi tekrar çalıştırın. Bazel şu hatayı verir:

    Rule '//toolchain:k8_toolchain_config' does not exist
    

    Ardından, toolchain/BUILD dosyasına bir ":k8_toolchain_config" hedefi ekleyin:

    filegroup(name = "k8_toolchain_config")
    
  6. Derlemeyi tekrar çalıştırın. Bazel aşağıdaki hatayı verir:

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

    CcToolchainConfigInfo, C++ araç zincirlerinizi yapılandırmak için kullandığınız bir sağlayıcıdır. Bu hatayı düzeltmek için aşağıdaki içeriğe sahip bir toolchain/cc_toolchain_config.bzl dosyası oluşturarak Bazel'e CcToolchainConfigInfo sağlayan bir Starlark kuralı oluşturun:

    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(), gerekli sağlayıcıyı oluştururCcToolchainConfigInfo. cc_toolchain_config kuralını kullanmak için toolchains/BUILD öğesine bir yükleme ifadesi ekleyin:

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

    Ve "k8_toolchain_config" dosya grubunu bir cc_toolchain_config kuralı bildirimiyle değiştirin:

    cc_toolchain_config(name = "k8_toolchain_config")
    
  7. Derlemeyi tekrar çalıştırın. Bazel şu hatayı verir:

    .../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`
    

    Bu noktada Bazel, kodu oluşturmayı denemek için yeterli bilgiye sahip ancak gerekli derleme işlemlerini tamamlamak için hangi araçları kullanması gerektiğini bilmiyor. Bazel'e hangi araçları kullanacağını bildirmek için Starlark kural uygulamasını değiştireceksiniz. Bunun için @bazel_tools//tools/cpp:cc_toolchain_config_lib.bzl'teki tool_path() oluşturucusuna ihtiyacınız vardır:

    # 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 ve /usr/bin/ld'un sisteminiz için doğru yollar olduğundan emin olun.

  8. Derlemeyi tekrar çalıştırın. Bazel şu hatayı verir:

     ..../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'in, dahil edilen üstbilgilerin nerede aranacağını bilmesi gerekir. Bu sorunu çözmenin birden fazla yolu vardır (ör. cc_binary öğesinin includes özelliğini kullanmak). Ancak burada bu sorun, cc_common.create_cc_toolchain_config_info öğesinin cxx_builtin_include_directories parametresi kullanılarak araç zinciri düzeyinde çözülmüştür. clang'ün farklı bir sürümünü kullanıyorsanız dahil etme yolunun farklı olacağına dikkat edin. Bu yollar ayrıca dağıtıma bağlı olarak farklı olabilir.

    toolchain/cc_toolchain_config.bzl içindeki döndürülen değeri aşağıdaki gibi değiştirin:

     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. Derleme komutunu tekrar çalıştırdığınızda şuna benzer bir hata gösterilir:

    /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'
    

    Bunun nedeni, C++ standart kitaplığının bağlayıcıda bulunmaması ve bağlayıcının bu kitaplığın simgelerini bulamamasıdır. Bu sorunu çözmenin birçok yolu vardır. Örneğin, cc_binary öğesinin linkopts özelliğini kullanabilirsiniz. Bu yöntemde sorun, araç zincirini kullanan herhangi bir hedefin bu işareti belirtmek zorunda kalmamasıyla çözülür.

    Aşağıdaki kodu cc_toolchain_config.bzl adlı cihaza kopyalayın:

      # 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 dosyasını çalıştırırsanız sonunda derlenir.

Çalışmanızı inceleyin

Bu eğiticide temel bir C++ araç zincirinin nasıl yapılandırılacağını öğrendiniz. Ancak araç zincirleri, bu basit örnekten daha güçlüdür.

Temel çıkarımlar: - Komut satırında, cc_toolchain_suite öğesine işaret etmesi gereken bir --crosstool_top işareti belirtmeniz gerekir - .bazelrc dosyasını kullanarak belirli bir yapılandırma için kısayol oluşturabilirsiniz - cc_toolchain_suite, farklı CPU'lar ve derleyiciler için cc_toolchains listeleyebilir. Farklılaştırmak için --cpu gibi komut satırı işaretlerini kullanabilirsiniz. - Araç zincirine, araçların nerede bulunduğunu bildirmeniz gerekir. Bu eğitimde, sistemdeki araçlara eriştiğiniz basitleştirilmiş bir sürüm bulunmaktadır. Daha bağımsız bir yaklaşımla ilgileniyorsanız buradan çalışma alanları hakkında bilgi edinebilirsiniz. Araçlarınız farklı bir çalışma alanından gelebilir ve bu araçların dosyalarını compiler_files gibi özelliklere hedef bağımlılıklarla cc_toolchain üzerinde kullanılabilir hale getirmeniz gerekir. tool_paths değerinin de değiştirilmesi gerekir. - Bağlantı oluşturma veya başka bir işlem türü olsun, farklı işlemlere hangi işaretlerin iletileceğini özelleştirmek için özellikler oluşturabilirsiniz.

Daha fazla bilgi

Daha fazla bilgi için C++ araç zinciri yapılandırması başlıklı makaleyi inceleyin.