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 Nightly · 8.4 · 8.3 · 8.2 · 8.1 · 8.0 · 7.6

Hướng dẫn này sử dụng một tình huống ví dụ để mô tả cách định cấu hình chuỗi công cụ C++ cho một dự án.

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 xây dựng
  • Sử dụng --toolchain_resolution_debug để gỡ lỗi độ phân giải chuỗi công cụ
  • Định cấu hình chuỗi công cụ C++
  • Tạo một quy tắc Starlark cung cấp cấu hình bổ sung cho cc_toolchain để Bazel có thể tạo ứng dụng bằng clang
  • Tạo tệp nhị phân C++ bằng cách chạy bazel build //main:hello-world trên máy Linux
  • Biên dịch chéo tệp nhị phân cho Android bằng cách chạy bazel build //main:hello-world --platforms=//:android_x86_64

Trước khi bắt đầu

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 phù hợp. Hướng dẫn này sử dụng clang version 19 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 xây dựng

Thiết lập môi trường xây dựng như sau:

  1. Nếu bạn chưa thực hiện, hãy tải xuống và cài đặt Bazel 7.0.2 trở lên.

  2. Thêm một tệp MODULE.bazel trống vào thư mục gốc.

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

    cc_binary(
        name = "hello-world",
        srcs = ["hello-world.cc"],
    )
    

    Vì Bazel sử dụng nhiều công cụ nội bộ được viết bằng C++ trong quá trình tạo, chẳng hạn như process-wrapper, 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ữ. Điều này cho phép các công cụ nội bộ này tạo bằng cách sử dụng chuỗi công cụ của công cụ được tạo trong hướng dẫn này. Do đó, mục tiêu cc_binary cũng được tạo bằng chuỗi công cụ mặc định.

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

    bazel build //main:hello-world
    

    Bản dựng thành công mà không có bất kỳ chuỗi công cụ nào được đăng ký trong MODULE.bazel.

    Để xem thêm những gì bên dưới, hãy chạy:

    bazel build //main:hello-world --toolchain_resolution_debug='@bazel_tools//tools/cpp:toolchain_type'
    
    INFO: ToolchainResolution: Target platform @@platforms//host:host: Selected execution platform @@platforms//host:host, type @@bazel_tools//tools/cpp:toolchain_type -> toolchain @@bazel_tools+cc_configure_extension+local_config_cc//:cc-compiler-k8
    

    Nếu không chỉ định --platforms, Bazel sẽ tạo mục tiêu cho @platforms//host bằng cách sử dụng @bazel_tools+cc_configure_extension+local_config_cc//:cc-compiler-k8.

Đị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ư mô tả dưới đây.

Thư viện này cũng giả định clang version 9.0.1, mặc dù thông tin chi tiết chỉ thay đổi một chút giữa các phiên bản clang.

  1. Thêm toolchain/BUILD bằng

    filegroup(name = "empty")
    
    cc_toolchain(
        name = "linux_x86_64_toolchain",
        toolchain_identifier = "linux_x86_64-toolchain",
        toolchain_config = ":linux_x86_64_toolchain_config",
        all_files = ":empty",
        compiler_files = ":empty",
        dwp_files = ":empty",
        linker_files = ":empty",
        objcopy_files = ":empty",
        strip_files = ":empty",
        supports_param_files = 0,
    )
    
    toolchain(
        name = "cc_toolchain_for_linux_x86_64",
        toolchain = ":linux_x86_64_toolchain",
        toolchain_type = "@bazel_tools//tools/cpp:toolchain_type",
        exec_compatible_with = [
            "@platforms//cpu:x86_64",
            "@platforms//os:linux",
        ],
        target_compatible_with = [
            "@platforms//cpu:x86_64",
            "@platforms//os:linux",
        ],
    )
    

    Sau đó, hãy thêm các phần phụ thuộc thích hợp và đăng ký chuỗi công cụ bằng MODULE.bazel bằng

    bazel_dep(name = "platforms", version = "0.0.10")
    register_toolchains(
        "//toolchain:cc_toolchain_for_linux_x86_64"
    )
    

    Bước này xác định một cc_toolchain và liên kết cc_toolchain đó với một mục tiêu toolchain cho cấu hình máy chủ.

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

    ERROR: toolchain/BUILD:4:13: in toolchain_config attribute of cc_toolchain rule //toolchain:linux_x86_64_toolchain: rule '//toolchain:linux_x86_64_toolchain_config' does not exist.
    
  3. Trong tệp toolchain/BUILD, hãy xác định một filegroup trống như sau:

    package(default_visibility = ["//visibility:public"])
    
    filegroup(name = "linux_x86_64_toolchain_config")
    
  4. Chạy lại bản dựng. Bazel sẽ phát ra lỗi sau:

    '//toolchain:linux_x86_64_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++. Để 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 một 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 một câu lệnh tải vào toolchain/BUILD ngay bên dưới câu lệnh gói:

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

    Sau đó, thay thế filegroup "linux_x86_64_toolchain_config" bằng một khai báo về quy tắc cc_toolchain_config:

    cc_toolchain_config(name = "linux_x86_64_toolchain_config")
    
  5. Chạy lại bản dựng. Bazel sẽ phát 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 không biết nên dùng công cụ nào để hoàn tất các thao tác cần thiết khi tạo. Bạn sẽ sửa đổi việc triển khai quy tắc Starlark để cho Bazel biết những công cụ cần sử dụng. Để làm việc đó, bạn cần 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",  # Compiler is referenced by the name "gcc" for historic reasons.
                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. Xin lưu ý rằng trình biên dịch được tham chiếu theo tên "gcc" vì lý do liên quan đến trước đây.

  6. Chạy lại bản dựng. Bazel sẽ phát ra lỗi sau:

    ERROR: main/BUILD:3:10: Compiling main/hello-world.cc failed: absolute path inclusion(s) found in rule '//main:hello-world':
    the source file 'main/hello-world.cc' includes the following non-builtin files with absolute paths (if these are builtin files, make sure these paths are in your toolchain):
      '/usr/include/c++/13/ctime'
      '/usr/include/x86_64-linux-gnu/c++/13/bits/c++config.h'
      '/usr/include/x86_64-linux-gnu/c++/13/bits/os_defines.h'
      ...
    

    Bazel cần biết nơi tìm kiếm các tiêu đề được đưa vào. 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 một phiên bản khác của clang, thì đường dẫn include sẽ 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 để có dạng như sau:

    return cc_common.create_cc_toolchain_config_info(
        ctx = ctx,
        cxx_builtin_include_directories = [ # NEW
            "/usr/lib/llvm-19/lib/clang/19/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,
    )
    
  7. Chạy lại lệnh tạo, bạn sẽ thấy một lỗi như sau:

    /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à trình liên kết bị thiếu thư viện 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. Ở đây, 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ã sau vào toolchain/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",    # NEW
        "flag_group", # NEW
        "flag_set",   # NEW
        "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",  # Compiler is referenced by the name "gcc" for historic reasons.
                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-19/lib/clang/19/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],
    )
    

    Xin lưu ý rằng mã này sử dụng thư viện GNU C++ libstdc++. Nếu bạn muốn sử dụng thư viện LLVM C++, hãy dùng "-lc++" thay vì "-lstdc++".

  8. Khi chạy bazel build //main:hello-world, cuối cùng, thao tác này sẽ tạo thành công tệp nhị phân cho máy chủ.

  9. Trong toolchain/BUILD, hãy sao chép các mục tiêu cc_toolchain_config, cc_toolchaintoolchain rồi thay thế linux_x86_64 bằng android_x86_64 trong tên mục tiêu.

    Trong MODULE.bazel, hãy đăng ký chuỗi công cụ cho Android

    register_toolchains(
        "//toolchain:cc_toolchain_for_linux_x86_64",
        "//toolchain:cc_toolchain_for_android_x86_64"
    )
    
  10. Chạy bazel build //main:hello-world --android_platforms=//toolchain:android_x86_64 để tạo tệp nhị phân cho Android.

Trên thực tế, Linux và Android phải có các cấu hình chuỗi công cụ C++ khác nhau. Bạn có thể sửa đổi cc_toolchain_config hiện có cho các điểm khác biệt hoặc tạo các quy tắc riêng biệt (tức là nhà cung cấp CcToolchainConfigInfo) cho các nền tảng riêng biệt.

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ày.

Sau đây là những điểm chính cần ghi nhớ:

  • Bạn cần chỉ định một cờ platforms phù hợp trong dòng lệnh để Bazel phân giải thành chuỗi công cụ cho các giá trị ràng buộc tương tự trên nền tảng. Tài liệu này có thêm thông tin về các cờ cấu hình dành riêng cho ngôn ngữ.
  • Bạn phải cho chuỗi công cụ biết vị trí của các công cụ. 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ụ từ hệ thống. Nếu quan tâm đến một phương pháp độc lập hơn, bạn có thể đọc về các phần phụ thuộc bên ngoài. Các công cụ của bạn có thể đến từ một mô-đun 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 vào 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 những cờ cần đượ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 chi tiết, hãy xem phần Cấu hình chuỗi công cụ C++