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

Sử dụng bộ sưu tập để sắp xếp ngăn nắp các trang Lưu và phân loại nội dung dựa trên lựa chọn ưu tiên của bạn.
Báo cáo sự cố Xem nguồn

Hướng dẫn này sử dụng tình huống ví dụ để mô tả cách định cấu hình chuỗi công cụ C++ cho dự án. Dựa trên dự án C++ mẫu được thiết kế mà 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 quy tắc Starlark cung cấp thêm cấu hình cho cc_toolchain để Bazel có thể xây dựng ứng dụng bằng clang
  • Xác nhận kết quả dự kiến 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 bạn đang sử dụng Linux và đã xây dựng thành công các ứng dụng C++ cũng như cài đặt công cụ và thư viện thích hợp. Phần hướng dẫn sử dụng clang version 9.0.1 mà bạn có thể cài đặt trên hệ thống.

Hãy 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ục bộ 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 ở thư mục gốc của không gian làm việc với các nội dung sau để 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ột mục build:{config_name} --flag=value, cờ dòng lệnh --config={config_name} được liên kết với cờ đó. Xem tài liệu về các cờ được sử dụng: crosstool_top, cpuhost_crosstool_top.

Khi tạo mục tiêu bằng bazel build --config=clang_config //main:hello-world, Bazel sẽ 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ụ khác nhau cho các CPU khác nhau. Đó là lý do bộ công cụ này đượ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 trong C++ trong quá trình tạo, 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ì 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 như mô tả bên dưới.

  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 bên trong thư mục toolchain.

  2. Chạy lại bản dựng. Do gói toolchain chưa xác định mục tiêu clang_suite, nên Bazel sẽ gửi 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 tạo ra lỗi sau:

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

    Bazel phát hiện ra rằng cờ --crosstool_top trỏ đến một quy tắc không cung cấp 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 tệp toolchain/BUILD, hãy thay thế nhóm tệp trống bằng các mã sau:

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

    Thuộc tính toolchains sẽ tự động liên kết các giá trị --cpu (và cả --compiler khi được chỉ định) với cc_toolchain. Bạn chưa xác định bất kỳ mục tiêu cc_toolchain nào và Bazel sẽ nhanh chóng khiếu nại điều đó.

  4. Chạy lại bản dựng. Bazel tạo ra lỗi sau:

    Rule '//toolchain:k8_toolchain' does not exist
    

    Bây giờ, bạn cần xác định các mục tiêu cc_toolchain cho mọi giá trị trong thuộc tính cc_toolchain_suite.toolchains. Thêm nội dung sau đây vào 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 tạo ra 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 tạo ra lỗi sau:

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

    CcToolchainConfigInfo là nhà cung cấp mà bạn sử dụng để định cấu hình chuỗi công cụ C++. Để khắc phục lỗi này, hãy tạo quy tắc Starlark cung cấp CcToolchainConfigInfo cho Bazel bằng cách tạo 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 nhà cung cấp cần thiết CcToolchainConfigInfo. Để sử dụng quy tắc cc_toolchain_config, hãy thêm câu lệnh tải vào toolchains/BUILD:

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

    Và thay thế nhóm tệp "k8_toolchain_config" bằng nội dung khai báo của quy tắc cc_toolchain_config:

    cc_toolchain_config(name = "k8_toolchain_config")
    
  7. Chạy lại bản dựng. Bazel tạo 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 để cố gắng tạo mã nhưng vẫn chưa biết nên sử dụng công cụ nào để hoàn thành các hành động cần thiết trong bản dựng. Bạn sẽ sửa đổi phương thức triển khai quy tắc Starlark để cho Bazel biết công cụ nào cần sử dụng. Để làm được việc đó, 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
        )
    

    Hãy đả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 tạo 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 đề có liên quan. 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. Tuy nhiên, ở đây bạn có thể 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. Hãy lưu ý rằng nếu bạn đang sử dụng một phiên bản clang khác, thì đường dẫn bao gồm sẽ khác. Những đường dẫn này cũng có thể khác nhau, tùy thuộc vào cách phân phối.

    Sửa đổi giá trị trả về trong toolchain/cc_toolchain_config.bzl để có dạng 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 tạo bản dựng, bạn sẽ thấy 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'
    

    Nguyên nhân là do trình liên kết thiếu thư viện chuẩn C++ và không thể tìm thấy các ký hiệu của trình liên kết. 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. Ở đây, bạn có thể 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ụ không phải chỉ định cờ này.

    Sao chép mã sau 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 thì ứng dụng sẽ được tạo.

Kiểm tra bài tập của bạn

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ụ thì có tác động mạnh 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 cờ --crosstool_top trong dòng lệnh để trỏ đến cc_toolchain_suite - Bạn có thể tạo lối tắt cho một cấu hình cụ thể bằng cách sử dụng tệp .bazelrc - cc_toolchain_Suite có thể liệt kê cc_toolchains cho các CPU và trình biên dịch khác nhau. Bạn có thể dùng các cờ hiệu dòng lệnh như --cpu để phân biệt. – Bạn phải cho chuỗi công cụ biết vị trí xuất hiện của các công cụ này. Trong hướng dẫn này, có một phiên bản đơn giản để bạn truy cập vào các công cụ trong hệ thống. Nếu muốn sử dụng một phương pháp độc lập hơn, bạn có thể đọc thông tin 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 những công cụ đó cho cc_toolchain kèm theo các phần phụ thuộc mục tiêu cho 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 tính năng để tuỳ chỉnh cờ nào sẽ được chuyển đến các thao tác khác nhau, có thể là liên kết hoặc bất kỳ loại thao tác nào khác.

Đọc thêm

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