Nền tảng

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. Việc 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ề điều kiện ràng buộcnền tảng.

Điều kiện ràng buộc là một thuộc tính đặc biệt của bản dựng hoặc máy sản xuất. Các điều kiện ràng buộc phổ biến là cấu trúc CPU, sự hiện diện hoặc vắng mặt của GPU hoặc phiên bản của trình biên dịch được cài đặt cục bộ. Tuy nhiên, điều kiện ràng buộc có thể là bất cứ điều gì giúp phân biệt các máy một cách có ý nghĩa 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 điều kiện 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 những máy mà họ muốn xây dựng, những máy sẽ chạy các thao tác biên dịch và kiểm thử, cũng như những chuỗi công cụ mà các thao tác xây dựng sẽ biên dịch.

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 trên 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ể đóng:

  • Máy chủ – Nền tảng mà chính Bazel chạy.
  • Thực thi – Nền tảng chạy các thao tác biên dịch để tạo dữ liệu đầu ra của bản dựng.
  • Mục tiêu – Nền tảng mà mã đang được xây dựng sẽ chạy.

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 máy chủ, thực thi và mục tiêu giống nhau. Ví dụ: xây dựng trên máy của nhà phát triển mà không cần thực thi từ xa, sau đó chạy tệp nhị phân đã xây dựng trên cùng một máy.

  • Bản dựng biên dịch chéo – Nền tảng máy chủ và thực thi giống nhau, nhưng nền tảng mục tiêu khác nhau. Ví dụ: xây dựng ứng dụng iOS trên Macbook Pro mà không cần thực thi từ xa.

  • Bản dựng nhiều nền tảng – Nền tảng máy chủ, thực thi và mục tiêu đều khác nhau. Ví dụ: xây dựng ứ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 nền tảng là chỉ định các máy mục tiêu 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 riêng vì thiết lập máy xây dựng khác nhau giữa các tổ chức.

Khi --platforms không được đặt, giá trị mặc định sẽ là @platforms//host. Giá trị này được xác định đặc biệt để tự động phát hiện các thuộc tính CPU và hệ điều hành của máy chủ để bản dựng nhắm đến cùng một máy mà Bazel chạy. Quy tắc xây dựng có thể chọn các thuộc tính này bằng các @platforms/os@platforms/cpu điều kiện ràng buộc.

Các điều kiện ràng buộc và nền tảng hữu ích khác

Để 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 điều kiện ràng buộc cho các cấu trúc CPU và hệ điều hành phổ biến nhất. Tất cả các định nghĩa này đều có 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 CPU và hệ điều hành của máy mà Bazel chạy.

Xác định điều kiện ràng buộc

Các điều kiện ràng buộc được mô hình hoá bằng các quy tắc xây dựng constraint_settingconstraint_value.

constraint_setting khai báo một loại thuộc tính. 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 giá trị này dưới dạng nhãn khi xác định nền tảng hoặc tuỳ chỉnh 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 điều kiện ràng buộc x86 dưới dạng //cpus:x86.

Nếu chế độ hiển thị cho phép, bạn có thể mở rộng constraint_setting hiện có bằng cách xác định giá trị riêng cho điều kiện ràng buộc đó.

Xác định nền tảng

Quy tắc xây dựng platform xác định một nền tảng là một tập hợp các constraint_values:

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//cpus:x86.

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 xây dựng cho một nền tảng mục tiêu cụ thể, bạn thường nên bỏ qua các 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ó thể sẽ tạo ra nhiều lỗi trình biên dịch khi xây dựng trên máy Linux bằng //.... Sử dụng thuộc tính target_compatible_with để cho Bazel biết các điều kiện ràng buộc của 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 xây dựng cho bất kỳ nền tảng nào không đáp ứng tất cả các điều kiện ràng buộc. Ví dụ sau đây hạn chế win_driver_lib.cc cho 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_libchỉ tương thích để xây dựng với 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 bắc cầu 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 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 việc mở rộng mẫu mục tiêu. Ví dụ: 2 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 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, các mục tiêu test_suite trên dòng lệnh hoạt động giống như :all.... Việc sử dụng --noexpand_test_suites sẽ ngă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 không tương thích được chỉ định rõ ràng sẽ bị bỏ qua một cách âm thầm nếu --skip_incompatible_explicit_targets được bật.

Các điều kiện ràng buộc biểu cảm hơn

Để linh hoạt hơn trong việc biểu thị các điều kiện ràng buộc, hãy sử 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 để biểu thị các hạn chế phức tạp hơn. Ví dụ: sử dụng để 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 như sau:

  1. Khi nhắm mục tiêu đến macOS, mục tiêu không có điều kiện ràng buộc.
  2. Khi nhắm mục tiêu đến Linux, mục tiêu không có điều kiện ràng buộc.
  3. Nếu không, mục tiêu sẽ có điều kiện ràng buộc @platforms//:incompatible. Vì @platforms//:incompatible khô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 dễ đọc hơn, hãy sử dụng skylib's selects.with_or().

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 bazel cquery

Bạn có thể sử dụng IncompatiblePlatformProvider trong bazel cquery's định dạng đầu ra Starlark để 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ể sử dụng tính năng này để lọc các mục tiêu không tương thích. Ví dụ bên dưới 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 sẽ bỏ qua các hạn chế về chế độ hiển thị.