Hướng dẫn về Bazel: Định cấu hình chuỗi công cụ C++

Báo cáo vấn đề Xem nguồn Hằng đêm · 7,4 của Google. 7.3 · 7.2 · 7.1 · 7.0 · 6.5

Hướng dẫn này sử dụng một tình huống mẫu để mô tả cách định cấu hình C++ cho một dự án. Chiến dịch này dựa trên một ví dụ về dự án C++ bản dựng không có lỗi bằng clang.

Kiến thức bạn sẽ học được

Trong hướng dẫn này, bạn sẽ tìm hiểu cách:

  • Thiết lập môi trường tạo bản dựng
  • Định cấu hình chuỗi công cụ C++
  • Tạo một quy tắc Starlark cung cấp thêm cấu hình cho cc_toolchain để Bazel có thể tạo ứng dụng bằng clang
  • Xác nhận kết quả mong đợi bằng cách chạy bazel build --config=clang_config //main:hello-world trên máy Linux
  • Xây dựng ứng dụng C++

Trước khi bắt đầu

Thiết lập môi trường tạo bản dựng

Hướng dẫn này giả định rằng bạn đang sử dụng Linux và đã tạo thành công các ứng dụng C++ cũng như cài đặt các công cụ và thư viện thích hợp. Hướng dẫn này sử dụng clang version 9.0.1 mà bạn có thể cài đặt trên hệ thống của mình.

Thiết lập môi trường tạo bản dựng như sau:

  1. Tải xuống và cài đặt Bazel 0.23 trở lên nếu bạn chưa thực hiện.

  2. Tải dự án C++ mẫu xuống từ GitHub và đặt dự án đó vào một thư mục trống trên máy của bạn.

  3. Thêm mục tiêu cc_binary sau vào tệp main/BUILD:

    cc_binary(
        name = "hello-world",
        srcs = ["hello-world.cc"],
    )
    
  4. Tạo tệp .bazelrc ở gốc của thư mục không gian làm việc bằng những nội dung sau đây để cho phép sử dụng cờ --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
    

Đối với mục build:{config_name} --flag=value, cờ dòng lệnh --config={config_name} được liên kết với cờ cụ thể đó. Xem tài liệu về các cờ được sử dụng: crosstool_top, cpuhost_crosstool_top.

Khi bạn xây dựng mục tiêu của mình với bazel build --config=clang_config //main:hello-world, Bazel sử dụng chuỗi công cụ tuỳ chỉnh từ cc_toolchain_suite //toolchain:clang_suite. Bộ công cụ này có thể liệt kê các chuỗi công cụ cho nhiều CPU, và đó là lý do chúng được phân biệt với cờ --cpu=k8.

Vì Bazel sử dụng nhiều công cụ nội bộ được viết bằng C++ trong quá trình xây dựng, chẳng hạn như trình bao bọc quy trình, nên chuỗi công cụ C++ mặc định có sẵn được chỉ định cho nền tảng lưu trữ để các công cụ này được tạo bằng chuỗi công cụ đó thay vì chuỗi công cụ được tạo trong hướng dẫn này.

Định cấu hình chuỗi công cụ C++

Để định cấu hình chuỗi công cụ C++, hãy liên tục tạo ứng dụng và loại bỏ từng lỗi một như được mô tả dưới đây.

  1. Chạy bản dựng bằng lệnh sau:

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

    Vì bạn đã chỉ định --crosstool_top=//toolchain:clang_suite trong tệp .bazelrc, nên Bazel sẽ gửi lỗi sau:

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

    Trong thư mục không gian làm việc, hãy tạo thư mục toolchain cho gói và một tệp BUILD trống trong thư mục toolchain.

  2. Chạy lại bản dựng. Vì gói toolchain chưa xác định clang_suite mục tiêu, Bazel sẽ đưa ra lỗi sau:

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

    Trong tệp toolchain/BUILD, hãy xác định một nhóm tệp trống như sau:

    package(default_visibility = ["//visibility:public"])
    
    filegroup(name = "clang_suite")
    
  3. Chạy lại bản dựng. Bazel sẽ gửi lỗi sau:

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

    Bazel phát hiện thấy cờ --crosstool_top trỏ đến một quy tắc không cung cấp trình cung cấp ToolchainInfo cần thiết. Vì vậy, bạn cần trỏ --crosstool_top đến một quy tắc cung cấp ToolchainInfo – đó là quy tắc cc_toolchain_suite. Trong toolchain/BUILD, hãy thay thế nhóm tệp trống bằng:

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

    Thuộc tính toolchains tự động liên kết các giá trị --cpu (và cũng là --compiler khi được chỉ định) với cc_toolchain. Bạn chưa xác định mọi mục tiêu cc_toolchain và Bazel sẽ phàn nàn về điều đó trong thời gian ngắn.

  4. Chạy lại bản dựng. Bazel đưa ra lỗi sau:

    Rule '//toolchain:k8_toolchain' does not exist
    

    Bây giờ, bạn cần xác định mục tiêu cc_toolchain cho mọi giá trị trong Thuộc tính cc_toolchain_suite.toolchains. Thêm đoạn mã sau vào phần Tệp 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. Chạy lại bản dựng. Bazel sẽ gửi lỗi sau:

    Rule '//toolchain:k8_toolchain_config' does not exist
    

    Tiếp theo, hãy thêm mục tiêu ":k8_toolchain_config" vào tệp toolchain/BUILD:

    filegroup(name = "k8_toolchain_config")
    
  6. Chạy lại bản dựng. Bazel sẽ gửi lỗi sau:

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

    CcToolchainConfigInfo là một trình cung cấp mà bạn dùng để định cấu hình chuỗi công cụ C++ của bạn. Để khắc phục lỗi này, hãy tạo một quy tắc Starlark cung cấp CcToolchainConfigInfo cho Bazel bằng cách tạo ra Tệp toolchain/cc_toolchain_config.bzl có nội dung sau:

    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() tạo trình cung cấp cần thiết CcToolchainConfigInfo. Để sử dụng quy tắc cc_toolchain_config, hãy thêm một câu lệnh tải vào toolchains/BUILD:

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

    Và thay thế "k8_toolchain_config" với một phần khai báo cc_toolchain_config quy tắc:

    cc_toolchain_config(name = "k8_toolchain_config")
    
  7. Chạy lại bản dựng. Bazel đưa ra lỗi sau:

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

    Tại thời điểm này, Bazel đã có đủ thông tin để thử tạo mã nhưng vẫn chưa biết nên sử dụng công cụ nào để hoàn tất các thao tác tạo bản dựng bắt buộc. Bạn sẽ sửa đổi quy trình triển khai quy tắc Starlark để cho Bazel biết những gì để sử dụng. Để làm được điều đó, bạn cần có hàm khởi tạo tool_path() từ @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
        )
    

    Đảm bảo rằng /usr/bin/clang/usr/bin/ld là các đường dẫn chính xác cho hệ thống của bạn.

  8. Chạy lại bản dựng. Bazel đưa ra lỗi sau:

     ..../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 cần biết nơi tìm kiếm các tiêu đề đi kèm. Có nhiều cách để giải quyết vấn đề này, chẳng hạn như sử dụng thuộc tính includes của cc_binary, nhưng ở đây, vấn đề này được giải quyết ở cấp chuỗi công cụ bằng tham số cxx_builtin_include_directories của cc_common.create_cc_toolchain_config_info. Xin lưu ý rằng nếu bạn đang sử dụng phiên bản khác của clang, đường dẫn bao gồm sẽ là khác. Các đường dẫn này cũng có thể khác nhau tuỳ thuộc vào bản phân phối.

    Sửa đổi giá trị trả về trong toolchain/cc_toolchain_config.bzl để xem như sau:

     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. Chạy lại lệnh bản dựng, bạn sẽ thấy một lỗi như:

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

    Lý do là do trình liên kết thiếu thư viện tiêu chuẩn C++ và không tìm thấy các biểu tượng của thư viện này. Có nhiều cách để giải quyết vấn đề này, chẳng hạn như sử dụng thuộc tính linkopts của cc_binary. Vấn đề này được giải quyết bằng cách đảm bảo rằng mọi mục tiêu sử dụng chuỗi công cụ đều không phải chỉ định cờ này.

    Sao chép mã nguồn sau đây vào 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. Nếu bạn chạy bazel build --config=clang_config //main:hello-world, cuối cùng, bản dựng sẽ được tạo.

Xem lại bài tập

Trong hướng dẫn này, bạn đã tìm hiểu cách định cấu hình một chuỗi công cụ C++ cơ bản, nhưng chuỗi công cụ mạnh mẽ hơn ví dụ đơn giản này.

Những điểm chính cần ghi nhớ là: – Bạn cần chỉ định một cờ --crosstool_top trong dòng lệnh để trỏ đến một cc_toolchain_suite – Bạn có thể tạo lối tắt cho một cấu hình cụ thể bằng .bazelrc tệp – cc_toolchain_suite có thể liệt kê cc_toolchains cho nhiều CPU và trình biên dịch. Bạn có thể sử dụng cờ dòng lệnh như --cpu để phân biệt. – Bạn phải cho chuỗi công cụ biết vị trí của các công cụ. Nội dung của hướng dẫn này có một phiên bản đơn giản giúp bạn truy cập vào các công cụ từ hệ thống. Nếu bạn quan tâm đến một phương pháp độc lập hơn, bạn có thể đọc về không gian làm việc tại đây. Các công cụ của bạn có thể đến từ một không gian làm việc khác và bạn sẽ phải cung cấp các tệp của chúng cho cc_toolchain với các phần phụ thuộc mục tiêu trên các thuộc tính, chẳng hạn như compiler_files. Bạn cũng cần thay đổi tool_paths. – Bạn có thể tạo các tính năng để tuỳ chỉnh cờ nào sẽ được truyền đến các thao tác khác nhau, cho dù đó là liên kết hay bất kỳ loại thao tác nào khác.

Tài liệu đọc thêm

Để biết thêm thông tin, hãy xem Cấu hình chuỗi công cụ C++