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 một dự án. Nó dựa trên một dự án C++ mẫu không có lỗi khi xây dựng 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 cấu hình bổ sung cho
cc_toolchain
để Bazel có thể xây dựng ứng dụng bằngclang
- 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 ứ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 sử dụng clang version 9.0.1
mà bạn có thể cài đặt trên hệ thống.
Thiết lập môi trường tạo bản dựng như sau:
Tải xuống và cài đặt Bazel 0.23 trở lên nếu bạn chưa thực hiện việc này.
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.
Thêm mục tiêu
cc_binary
sau vào tệpmain/BUILD
:cc_binary( name = "hello-world", srcs = ["hello-world.cc"], )
Tạo một tệp
.bazelrc
ở gốc thư mục không gian làm việc có 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ờ cụ thể đó. Xem
tài liệu về các cờ được sử dụng:
crosstool_top
,
cpu
và
host_crosstool_top
.
Khi bạn 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 của bạn từ cc_toolchain_ suite.//toolchain:clang_suite
. Bộ dịch vụ 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 tại sao bộ công cụ này được phân biệt bằng 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 tạo bản 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 xây dựng ứng dụng và loại bỏ từng lỗi theo mô tả dưới đây.
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ặp 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ệpBUILD
trống bên trong thư mụctoolchain
.Chạy lại bản dựng. Vì gói
toolchain
chưa xác định mục tiêuclang_suite
, nên Bazel sẽ gặp 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")
Chạy lại bản dựng. Bazel báo lỗi sau:
'//toolchain:clang_suite' does not have mandatory providers: 'ToolchainInfo'
Bazel nhận thấy cờ
--crosstool_top
trỏ đến một quy tắc không cung cấp nhà cung cấpToolchainInfo
cần thiết. Vì vậy, bạn cần trỏ--crosstool_top
đến một quy tắc cung cấpToolchainInfo
– đó là quy tắccc_toolchain_suite
. Trong tệptoolchain/BUILD
, hãy thay thế nhóm tệp trống bằng tệp sau: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ả--compiler
khi được chỉ định) vớicc_toolchain
. Bạn chưa xác định mục tiêucc_toolchain
nào và Bazel sẽ nhanh chóng khiếu nại điều đó.Chạy lại bản dựng. Bazel báo 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ínhcc_toolchain_suite.toolchains
. Thêm mã sau vào tệptoolchain/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, )
Chạy lại bản dựng. Bazel báo 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")
Chạy lại bản dựng. Bazel báo 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ấpCcToolchainConfigInfo
cho Bazel bằng cách tạo tệptoolchain/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ếtCcToolchainConfigInfo
. Để sử dụng quy tắccc_toolchain_config
, hãy thêm câu lệnh tải vàotoolchain/BUILD
ngay bên dưới câu lệnh gói:load(":cc_toolchain_config.bzl", "cc_toolchain_config")
Và thay thế nhóm tệp "k8_toolchain_config" bằng một nội dung khai báo của quy tắc
cc_toolchain_config
:cc_toolchain_config(name = "k8_toolchain_config")
Chạy lại bản dựng. Bazel báo 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 xây dựng bắt buộc. 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
và/usr/bin/ld
là đường dẫn chính xác cho hệ thống của bạn.Chạy lại bản dựng. Bazel báo 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 vị trí để tìm kiếm tiêu đề được bao gồ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ủacc_binary
, nhưng ở đây chúng tôi giải quyết vấn đề này ở cấp chuỗi công cụ bằng tham sốcxx_builtin_include_directories
củacc_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ủaclang
, thì đường dẫn bao gồm sẽ khác. Các đường dẫn này cũng có thể khác nhau tuỳ thuộc vào mức phân bổ.Hãy 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, )
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'
Lý do là trình liên kết thiếu thư viện chuẩn C++ nên 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ủacc_binary
. Ở đây, bạn có thể giải quyết vấn đề này 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
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], )
Nếu bạn chạy
bazel build --config=clang_config //main:hello-world
, thì tính năng này cuối cùng sẽ được tạo.
Xem lại 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 chuỗi công cụ C++ cơ bản, nhưng chuỗi công cụ sẽ mạnh mẽ hơn so với 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.
Dòng này sẽ 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 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ể sử dụng 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í tồn tại 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ụ 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ề
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 công cụ đó
cho cc_toolchain
kèm theo các phần phụ thuộc mục tiêu về 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ờ nào được chuyển sang 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.
Tài liệu đọc thêm
Để biết thêm thông tin, hãy xem phần Cấu hình chuỗi công cụ C++