Giới thiệu
Bazel có thể tạo và kiểm thử mã trên nhiều loại phần cứng, hệ điều hành và cấu hình hệ thống. Điều này có thể liên quan đến nhiều phiên bản của các công cụ xây dựng như trình liên kết và trình biên dịch. Để giúp quản lý sự phức tạp này, Bazel có các khái niệm về constraints (ràng buộc) và platforms (nền tảng).
Ràng buộc là một thuộc tính đặc trưng của máy sản xuất hoặc máy dựng. Các ràng buộc phổ biến là cấu trúc CPU, sự hiện diện hoặc không có GPU hoặc phiên bản của trình biên dịch được cài đặt cục bộ. Nhưng các ràng buộc có thể là bất cứ điều gì phân biệt một cách có ý nghĩa các máy khi điều phối công việc xây dựng.
Nền tảng là một tập hợp các ràng buộc chỉ định một máy hoàn chỉnh. Bazel sử dụng khái niệm này để cho phép nhà phát triển chọn máy mà họ muốn tạo, máy nào sẽ chạy các thao tác biên dịch và kiểm thử, cũng như chuỗi công cụ nào sẽ biên dịch các thao tác tạo.
Nhà phát triển cũng có thể sử dụng các điều kiện ràng buộc để chọn các thuộc tính hoặc phần phụ thuộc tuỳ chỉnh cho các quy tắc xây dựng của họ. Ví dụ: "sử dụng src_arm.cc khi bản dựng nhắm đến một máy Arm".
Loại nền tảng
Bazel nhận ra 3 vai trò mà một nền tảng có thể đảm nhận:
- Máy chủ – Nền tảng mà Bazel chạy trên đó.
- Thực thi – Nền tảng chạy các thao tác biên dịch để tạo ra dữ liệu đầu ra của bản dựng.
- Đích đến – Nền tảng mà mã đang được tạo sẽ chạy trên đó.
Các bản dựng thường có 3 loại mối quan hệ với nền tảng:
Bản dựng một nền tảng – Nền tảng lưu trữ, thực thi và mục tiêu đều giống nhau. Ví dụ: tạo trên máy phát triển mà không cần thực thi từ xa, sau đó chạy tệp nhị phân đã tạo trên cùng một máy.
Bản dựng biên dịch chéo – Nền tảng lưu trữ và nền tảng thực thi giống nhau, nhưng nền tảng đích thì khác. Ví dụ: tạo một ứng dụng iOS trên Macbook Pro mà không cần thực thi từ xa.
Bản dựng đa nền tảng – Nền tảng lưu trữ, thực thi và mục tiêu đều khác nhau. Ví dụ: tạo một ứng dụng iOS trên Macbook Pro và sử dụng các máy Linux từ xa để biên dịch các thao tác C++ không cần Xcode.
Chỉ định nền tảng
Cách phổ biến nhất để nhà phát triển sử dụng các nền tảng là chỉ định các máy đích mong muốn bằng cờ --platforms:
$ bazel build //:my_linux_app --platforms=//myplatforms:linux_x86
Các tổ chức thường duy trì định nghĩa nền tảng của riêng mình vì chế độ thiết lập máy dựng khác nhau giữa các tổ chức.
Khi bạn không đặt --platforms, giá trị mặc định sẽ là @platforms//host. Điều này được xác định cụ thể để tự động phát hiện các thuộc tính CPU và hệ điều hành của máy chủ lưu trữ, do đó, các bản dựng nhắm đến cùng một máy mà Bazel chạy trên đó. Các quy tắc xây dựng có thể chọn trên những thuộc tính này bằng các ràng buộc @platforms/os và @platforms/cpu.
Các ràng buộc và nền tảng thường hữu ích
Để duy trì tính nhất quán của hệ sinh thái, nhóm Bazel duy trì một kho lưu trữ với các định nghĩa về ràng buộc cho những cấu trúc CPU và hệ điều hành phổ biến nhất. Tất cả các nền tảng này đều được xác định trong https://github.com/bazelbuild/platforms.
Bazel đi kèm với định nghĩa nền tảng đặc biệt sau: @platforms//host (được đặt bí danh là @bazel_tools//tools:host_platform). Định nghĩa này tự động phát hiện các thuộc tính hệ điều hành và CPU của máy mà Bazel chạy trên đó.
Xác định các điều kiện ràng buộc
Các ràng buộc được mô hình hoá bằng quy tắc xây dựng constraint_setting và constraint_value.
constraint_setting khai báo một loại tài sản. Ví dụ:
constraint_setting(name = "cpu")
constraint_value khai báo một giá trị có thể có cho thuộc tính đó:
constraint_value(
name = "x86",
constraint_setting = ":cpu"
)
Bạn có thể tham chiếu các nhãn này khi xác định nền tảng hoặc tuỳ chỉnh các quy tắc xây dựng trên các nền tảng đó. Nếu các ví dụ trên được xác định trong cpus/BUILD, bạn có thể tham chiếu đến ràng buộc x86 dưới dạng //cpus:x86.
Nếu có thể nhìn thấy, bạn có thể mở rộng một constraint_setting hiện có bằng cách xác định giá trị của riêng bạn cho constraint_setting đó.
Xác định nền tảng
Quy tắc tạo platform xác định một nền tảng dưới dạng một tập hợp các constraint_value:
platform(
name = "linux_x86",
constraint_values = [
"//oses:linux",
"//cpus:x86",
],
)
Điều này mô hình hoá một máy phải có cả điều kiện ràng buộc //oses:linux và //cpus:x86.
Các nền tảng chỉ có thể có một constraint_value cho một constraint_setting nhất định.
Ví dụ: một nền tảng không thể có 2 CPU, trừ phi bạn tạo một loại constraint_setting khác để mô hình hoá giá trị thứ hai.
Bỏ qua các mục tiêu không tương thích
Khi tạo cho một nền tảng mục tiêu cụ thể, bạn nên bỏ qua những mục tiêu sẽ không bao giờ hoạt động trên nền tảng đó. Ví dụ: trình điều khiển thiết bị Windows của bạn có thể sẽ tạo ra nhiều lỗi trình biên dịch khi tạo trên máy Linux bằng //.... Sử dụng thuộc tính target_compatible_with để cho Bazel biết những ràng buộc về nền tảng mục tiêu mà mã của bạn có.
Cách sử dụng đơn giản nhất của thuộc tính này là hạn chế một mục tiêu cho một nền tảng duy nhất.
Mục tiêu sẽ không được tạo cho bất kỳ nền tảng nào không đáp ứng tất cả các ràng buộc. Ví dụ sau đây hạn chế win_driver_lib.cc đối với Windows 64 bit.
cc_library(
name = "win_driver_lib",
srcs = ["win_driver_lib.cc"],
target_compatible_with = [
"@platforms//cpu:x86_64",
"@platforms//os:windows",
],
)
:win_driver_lib chỉ tương thích với việc tạo bằng Windows 64 bit và không tương thích với mọi thứ khác. Tính không tương thích là tính chất bắc cầu. Mọi mục tiêu phụ thuộc gián tiếp vào một mục tiêu không tương thích đều được coi là không tương thích.
Khi nào mục tiêu bị bỏ qua?
Các mục tiêu sẽ bị bỏ qua khi được coi là không tương thích và được đưa vào bản dựng như một phần của quá trình mở rộng mẫu mục tiêu. Ví dụ: hai lệnh gọi sau đây sẽ bỏ qua mọi mục tiêu không tương thích được tìm thấy trong quá trình mở rộng mẫu mục tiêu.
$ bazel build --platforms=//:myplatform //...
$ bazel build --platforms=//:myplatform //:all
Các kiểm thử không tương thích trong test_suite cũng sẽ bị bỏ qua nếu test_suite được chỉ định trên dòng lệnh bằng --expand_test_suites.
Nói cách khác, test_suite nhắm đến dòng lệnh hoạt động như :all và .... Việc sử dụng --noexpand_test_suites sẽ ngăn chặn việc mở rộng và khiến các mục tiêu test_suite có các kiểm thử không tương thích cũng không tương thích.
Việc chỉ định rõ ràng một mục tiêu không tương thích trên dòng lệnh sẽ dẫn đến thông báo lỗi và bản dựng không thành công.
$ bazel build --platforms=//:myplatform //:target_incompatible_with_myplatform
...
ERROR: Target //:target_incompatible_with_myplatform is incompatible and cannot be built, but was explicitly requested.
...
FAILED: Build did NOT complete successfully
Các mục tiêu rõ ràng không tương thích sẽ bị bỏ qua một cách âm thầm nếu bạn bật --skip_incompatible_explicit_targets.
Các ràng buộc biểu cảm hơn
Để linh hoạt hơn trong việc thể hiện các ràng buộc, hãy dùng @platforms//:incompatible
constraint_value mà không nền tảng nào đáp ứng.
Sử dụng select() kết hợp với @platforms//:incompatible để thể hiện các hạn chế phức tạp hơn. Ví dụ: sử dụng nó để triển khai logic OR cơ bản. Sau đây đánh dấu một thư viện tương thích với macOS và Linux, nhưng không tương thích với các nền tảng khác.
cc_library(
name = "unixish_lib",
srcs = ["unixish_lib.cc"],
target_compatible_with = select({
"@platforms//os:osx": [],
"@platforms//os:linux": [],
"//conditions:default": ["@platforms//:incompatible"],
}),
)
Bạn có thể diễn giải thông tin trên như sau:
- Khi nhắm đến macOS, mục tiêu không có các ràng buộc.
- Khi nhắm đến Linux, mục tiêu không có các ràng buộc.
- Nếu không, đích đến sẽ có quy tắc ràng buộc
@platforms//:incompatible. Vì@platforms//:incompatiblekhông thuộc bất kỳ nền tảng nào, nên mục tiêu được coi là không tương thích.
Để giúp các điều kiện ràng buộc của bạn dễ đọc hơn, hãy sử dụng selects.with_or() của skylib.
Bạn có thể biểu thị khả năng tương thích nghịch đảo theo cách tương tự. Ví dụ sau đây mô tả một thư viện tương thích với mọi thứ ngoại trừ ARM.
cc_library(
name = "non_arm_lib",
srcs = ["non_arm_lib.cc"],
target_compatible_with = select({
"@platforms//cpu:arm": ["@platforms//:incompatible"],
"//conditions:default": [],
}),
)
Phát hiện các mục tiêu không tương thích bằng cách sử dụng bazel cquery
Bạn có thể sử dụng IncompatiblePlatformProvider trong định dạng đầu ra Starlark của bazel cquery để phân biệt các mục tiêu không tương thích với các mục tiêu tương thích.
Bạn có thể dùng tính năng này để lọc ra các mục tiêu không tương thích. Ví dụ dưới đây sẽ chỉ in nhãn cho các mục tiêu tương thích. Các mục tiêu không tương thích sẽ không được in.
$ cat example.cquery
def format(target):
if "IncompatiblePlatformProvider" not in providers(target):
return target.label
return ""
$ bazel cquery //... --output=starlark --starlark:file=example.cquery
Vấn đề đã biết
Các mục tiêu không tương thích bỏ qua các hạn chế về khả năng hiển thị.