Xây dựng bằng nền tảng

Bazel có khả năng hỗ trợ tinh vi cho việc mô hình hoá các nền tảngchuỗi công cụ. Việc tích hợp tính năng này với các dự án thực tế đòi hỏi sự hợp tác chặt chẽ giữa chủ sở hữu mã, người duy trì quy tắc và các nhà phát triển Bazel cốt lõi.

Trang này tóm tắt mục đích của các nền tảng và cho biết cách xây dựng bằng các nền tảng đó.

Tóm tắt: Các API nền tảng và chuỗi công cụ của Bazel hiện có sẵn nhưng sẽ không hoạt động ở mọi nơi cho đến khi tất cả các quy tắc ngôn ngữ, select() và các tài liệu tham khảo cũ khác được cập nhật. Công việc này vẫn đang được tiến hành. Cuối cùng, tất cả các bản dựng sẽ dựa trên nền tảng. Hãy đọc phần bên dưới để biết bản dựng của bạn phù hợp với tiêu chí nào.

Để xem tài liệu chính thức hơn, hãy tham khảo:

Thông tin khái quát

Nền tảngchuỗi công cụ được giới thiệu để chuẩn hoá cách các dự án phần mềm nhắm đến các máy khác nhau và tạo bằng các công cụ ngôn ngữ phù hợp.

Đây là một tính năng tương đối mới của Bazel. Ý tưởng này bắt nguồn từ việc nhận thấy rằng những người duy trì ngôn ngữ đã làm việc này theo cách đặc biệt và không tương thích. Ví dụ: các quy tắc C++ sử dụng --cpu--crosstool_top để đặt CPU mục tiêu và chuỗi công cụ C++ của bản dựng. Không có lựa chọn nào trong số này mô hình hoá chính xác một "nền tảng". Những nỗ lực trước đây để làm việc này đã tạo ra các bản dựng không chính xác và khó hiểu. Các cờ này cũng không kiểm soát quá trình biên dịch Java, vốn đã phát triển giao diện độc lập riêng với --java_toolchain.

Bazel được thiết kế cho các dự án lớn, đa ngôn ngữ và đa nền tảng. Điều này đòi hỏi sự hỗ trợ có nguyên tắc hơn cho các khái niệm này, bao gồm cả các API rõ ràng khuyến khích khả năng tương tác giữa ngôn ngữ và dự án. Đây là mục đích của các API mới này.

Di chuyển

Các API nền tảng và chuỗi công cụ chỉ hoạt động khi dự án thực sự sử dụng chúng. Đây không phải là điều đơn giản vì logic quy tắc, chuỗi công cụ, các phần phụ thuộc và select() của một dự án phải hỗ trợ các quy tắc này. Điều này đòi hỏi một trình tự di chuyển cẩn thận để đảm bảo tất cả dự án và các phần phụ thuộc của dự án đều hoạt động đúng cách.

Ví dụ: các nền tảng hỗ trợ Quy tắc C++ của Bazel. Nhưng Quy tắc của Apple thì không. Dự án C++ của bạn có thể không quan tâm đến Apple. Nhưng những nền tảng khác thì có thể. Vì vậy, bạn chưa thể bật nền tảng cho tất cả các bản dựng C++ trên toàn cầu.

Phần còn lại của trang này mô tả trình tự di chuyển này cũng như cách thức và thời điểm các dự án của bạn có thể phù hợp.

Mục tiêu

Quá trình di chuyển nền tảng của Bazel sẽ hoàn tất khi tất cả các dự án đều được tạo bằng biểu mẫu:

bazel build //:myproject --platforms=//:myplatform

Điều này có nghĩa là:

  1. Các quy tắc mà dự án của bạn sử dụng có thể suy ra các chuỗi công cụ chính xác từ //:myplatform.
  2. Các quy tắc mà các phần phụ thuộc của dự án sử dụng có thể suy ra các chuỗi công cụ chính xác từ //:myplatform.
  3. Một trong hai dự án tuỳ thuộc vào dự án của bạn hỗ trợ //:myplatform hoặc dự án của bạn hỗ trợ các API cũ (chẳng hạn như --crosstool_top).
  4. //:myplatform tham chiếu [các khai báo chung][Common Platform Declaration]{: .external} của CPU, OS và các khái niệm chung khác hỗ trợ khả năng tương thích tự động giữa các dự án.
  5. Tất cả các dự án liên quan select()s đều hiểu các thuộc tính của máy mà //:myplatform ngụ ý.
  6. //:myplatform được xác định ở một nơi rõ ràng, có thể sử dụng lại: trong kho lưu trữ của dự án nếu nền tảng này là duy nhất đối với dự án của bạn, nếu không thì ở nơi mà tất cả các dự án có thể sử dụng nền tảng này đều có thể tìm thấy.

Các API cũ sẽ bị xoá ngay khi đạt được mục tiêu này. Sau đó, đây sẽ là cách tiêu chuẩn để các dự án chọn nền tảng và chuỗi công cụ.

Tôi có nên sử dụng các nền tảng này không?

Nếu chỉ muốn tạo hoặc biên dịch chéo một dự án, bạn nên làm theo tài liệu chính thức của dự án đó.

Nếu là người duy trì dự án, ngôn ngữ hoặc chuỗi công cụ, thì cuối cùng bạn sẽ muốn hỗ trợ các API mới. Việc bạn chờ đến khi quá trình di chuyển trên toàn cầu hoàn tất hay chọn tham gia sớm sẽ phụ thuộc vào nhu cầu cụ thể về giá trị / chi phí của bạn:

Giá trị

  • Bạn có thể select() hoặc chọn chuỗi công cụ trên chính những thuộc tính mà bạn quan tâm thay vì các cờ được mã hoá cứng như --cpu. Ví dụ: nhiều CPU có thể hỗ trợ cùng một tập lệnh.
  • Bản dựng chính xác hơn. Nếu bạn select() với --cpu trong ví dụ trên, sau đó thêm một CPU mới hỗ trợ cùng một tập lệnh, thì select() sẽ không nhận dạng được CPU mới. Tuy nhiên, select() trên các nền tảng vẫn chính xác.
  • Trải nghiệm người dùng đơn giản hơn. Tất cả các dự án đều hiểu: --platforms=//:myplatform. Không cần nhiều cờ dành riêng cho ngôn ngữ trên dòng lệnh.
  • Thiết kế ngôn ngữ đơn giản hơn. Tất cả các ngôn ngữ đều dùng chung một API để xác định chuỗi công cụ, sử dụng chuỗi công cụ và chọn chuỗi công cụ phù hợp cho một nền tảng.
  • Bạn có thể bỏ qua các mục tiêu trong giai đoạn tạo và kiểm thử nếu chúng không tương thích với nền tảng mục tiêu.

Chi phí

  • Các dự án phụ thuộc chưa hỗ trợ nền tảng có thể không tự động hoạt động với dự án của bạn.
  • Để các tính năng này hoạt động, bạn có thể phải bảo trì tạm thời thêm.
  • Việc cùng tồn tại của các API mới và cũ đòi hỏi hướng dẫn người dùng cẩn thận hơn để tránh nhầm lẫn.
  • Định nghĩa chuẩn cho các thuộc tính chung như OSCPU vẫn đang phát triển và có thể yêu cầu thêm các đóng góp ban đầu.
  • Định nghĩa chuẩn cho chuỗi công cụ theo ngôn ngữ vẫn đang phát triển và có thể yêu cầu thêm các đóng góp ban đầu.

Đánh giá bằng API

platform là một tập hợp gồm constraint_value mục tiêu:

platform(
    name = "myplatform",
    constraint_values = [
        "@platforms//os:linux",
        "@platforms//cpu:arm",
    ],
)

constraint_value là một thuộc tính của máy. Các giá trị có cùng "loại" được nhóm trong một constraint_setting chung:

constraint_setting(name = "os")
constraint_value(
    name = "linux",
    constraint_setting = ":os",
)
constraint_value(
    name = "mac",
    constraint_setting = ":os",
)

toolchain là một quy tắc Starlark. Các thuộc tính của nó khai báo các công cụ của một ngôn ngữ (chẳng hạn như compiler = "//mytoolchain:custom_gcc"). Nhà cung cấp của nó truyền thông tin này đến các quy tắc cần được xây dựng bằng những công cụ này.

Chuỗi công cụ khai báo constraint_value của các máy mà chúng có thể nhắm đến (target_compatible_with = ["@platforms//os:linux"]) và các máy mà công cụ của chúng có thể chạy trên (exec_compatible_with = ["@platforms//os:mac"]).

Khi tạo $ bazel build //:myproject --platforms=//:myplatform, Bazel sẽ tự động chọn một chuỗi công cụ có thể chạy trên máy tạo và tạo các tệp nhị phân cho //:myplatform. Đây được gọi là phân giải chuỗi công cụ.

Bạn có thể đăng ký tập hợp các chuỗi công cụ có sẵn trong WORKSPACE bằng register_toolchains hoặc tại dòng lệnh bằng --extra_toolchains.

Hãy xem tại đây để tìm hiểu kỹ hơn.

Trạng thái

Phạm vi hỗ trợ nền tảng hiện tại sẽ khác nhau tuỳ theo ngôn ngữ. Tất cả các quy tắc chính của Bazel đều đang chuyển sang các nền tảng. Tuy nhiên, quá trình này sẽ mất thời gian. Điều này là do 3 lý do chính:

  1. Bạn phải cập nhật logic của quy tắc để lấy thông tin về công cụ từ toolchain API (ctx.toolchains) mới và ngừng đọc các chế độ cài đặt cũ như --cpu--crosstool_top. Việc này tương đối đơn giản.

  2. Người duy trì chuỗi công cụ phải xác định chuỗi công cụ và cung cấp cho người dùng quyền truy cập vào chuỗi công cụ (trong các mục WORKSPACE và kho lưu trữ GitHub). Về mặt kỹ thuật, việc này khá đơn giản nhưng bạn phải sắp xếp một cách thông minh để duy trì trải nghiệm dễ dàng cho người dùng.

    Bạn cũng cần có định nghĩa về nền tảng (trừ phi bạn tạo cho cùng một máy mà Bazel chạy trên đó). Nhìn chung, các dự án nên xác định nền tảng riêng.

  3. Bạn phải di chuyển các dự án hiện có. select()các hiệu ứng chuyển cảnh cũng phải được di chuyển. Đây là thách thức lớn nhất. Điều này đặc biệt khó khăn đối với các dự án đa ngôn ngữ (có thể không thành công nếu tất cả ngôn ngữ không đọc được --platforms).

Nếu đang thiết kế một bộ quy tắc mới, bạn phải hỗ trợ các nền tảng ngay từ đầu. Điều này tự động giúp các quy tắc của bạn tương thích với các quy tắc và dự án khác, đồng thời tăng giá trị khi API nền tảng trở nên phổ biến hơn.

Các tài sản phổ biến của nền tảng

Các thuộc tính nền tảng như OSCPU thường có trong các dự án nên được khai báo ở một vị trí tiêu chuẩn, tập trung. Điều này khuyến khích khả năng tương thích giữa các dự án và ngôn ngữ.

Ví dụ: nếu MyApp có một select() trên constraint_value @myapp//cpus:armSomeCommonLib có một select() trên @commonlib//constraints:arm, thì những select() này sẽ kích hoạt chế độ "arm" với các tiêu chí không tương thích.

Các thuộc tính chung trên toàn cầu được khai báo trong kho lưu trữ @platforms (vì vậy, nhãn chính tắc cho ví dụ trên là @platforms//cpu:arm). Các thuộc tính chung theo ngôn ngữ phải được khai báo trong kho lưu trữ của các ngôn ngữ tương ứng.

Nền tảng mặc định

Nhìn chung, chủ sở hữu dự án nên xác định các nền tảng rõ ràng để mô tả các loại máy mà họ muốn tạo. Sau đó, các hàm này sẽ được kích hoạt bằng --platforms.

Khi --platforms không được thiết lập, Bazel sẽ mặc định là platform đại diện cho máy tạo cục bộ. Thư mục này được tạo tự động tại @local_config_platform//:host nên bạn không cần xác định rõ ràng. Thao tác này ánh xạ OSCPU của máy cục bộ với constraint_value được khai báo trong @platforms.

C++

Các quy tắc C++ của Bazel sử dụng nền tảng để chọn chuỗi công cụ khi bạn đặt --incompatible_enable_cc_toolchain_resolution (#7260).

Điều này có nghĩa là bạn có thể định cấu hình một dự án C++ bằng:

bazel build //:my_cpp_project --platforms=//:myplatform

thay vì phiên bản cũ:

bazel build //:my_cpp_project` --cpu=... --crosstool_top=...  --compiler=...

Nếu dự án của bạn là C++ thuần tuý và không phụ thuộc vào các dự án không phải C++, thì bạn có thể sử dụng các nền tảng một cách an toàn miễn là selectquá trình chuyển đổi của bạn tương thích. Hãy xem #7260Định cấu hình chuỗi công cụ C++ để biết thêm hướng dẫn.

Chế độ này không được bật theo mặc định. Điều này là do các dự án của Apple vẫn định cấu hình phần phụ thuộc C++ bằng --cpu--crosstool_top (ví dụ). Vì vậy, điều này phụ thuộc vào việc các quy tắc của Apple có được chuyển sang các nền tảng hay không.

Java

Các quy tắc Java của Bazel sử dụng các nền tảng.

Thao tác này sẽ thay thế các cờ cũ --java_toolchain, --host_java_toolchain, --javabase--host_javabase.

Để tìm hiểu cách sử dụng cờ cấu hình, hãy xem hướng dẫn về Bazel và Java. Để biết thêm thông tin, hãy xem Tài liệu thiết kế.

Nếu bạn vẫn đang sử dụng cờ cũ, hãy làm theo quy trình di chuyển trong Vấn đề #7849.

Android

Các quy tắc Android của Bazel sử dụng nền tảng để chọn chuỗi công cụ khi bạn đặt --incompatible_enable_android_toolchain_resolution.

Tính năng này không được bật theo mặc định. Nhưng quá trình di chuyển đang diễn ra suôn sẻ.

Apple

Các quy tắc của Apple trong Bazel hiện chưa hỗ trợ các nền tảng để chọn chuỗi công cụ của Apple.

Chúng cũng không hỗ trợ các phần phụ thuộc C++ có hỗ trợ nền tảng vì chúng dùng --crosstool_top cũ để thiết lập chuỗi công cụ C++. Cho đến khi quá trình này được di chuyển, bạn có thể kết hợp các dự án Apple với C++ hỗ trợ nền tảng bằng ánh xạ nền tảng (ví dụ).

Ngôn ngữ khác

Nếu bạn đang thiết kế các quy tắc cho một ngôn ngữ mới, hãy sử dụng các nền tảng để chọn chuỗi công cụ của ngôn ngữ đó. Hãy xem tài liệu về chuỗi công cụ để biết hướng dẫn chi tiết.

select()

Bạn có thể select() dự án trên constraint_value mục tiêu nhưng không thể hoàn tất các nền tảng. Đây là điều có chủ ý để select() hỗ trợ nhiều loại máy nhất có thể. Một thư viện có các nguồn dành riêng cho ARM phải hỗ trợ tất cả các máy chạy ARM, trừ phi có lý do cụ thể hơn.

Để chọn một hoặc nhiều constraint_value, hãy sử dụng:

config_setting(
    name = "is_arm",
    constraint_values = [
        "@platforms//cpu:arm",
    ],
)

Điều này tương đương với việc chọn trên --cpu theo cách truyền thống:

config_setting(
    name = "is_arm",
    values = {
        "cpu": "arm",
    },
)

Xem thêm thông tin tại đây.

select trên --cpu, --crosstool_top, v.v. không hiểu --platforms. Khi di chuyển dự án sang các nền tảng, bạn phải chuyển đổi dự án sang constraint_values hoặc sử dụng các mối liên kết nền tảng để hỗ trợ cả hai kiểu thông qua cửa sổ di chuyển.

Kiểu chuyển cảnh

Các quá trình chuyển đổi Starlark thay đổi cờ xuống các phần của biểu đồ bản dựng. Nếu dự án của bạn sử dụng một hiệu ứng chuyển cảnh đặt --cpu, --crossstool_top hoặc các cờ cũ khác, thì các quy tắc đọc --platforms sẽ không thấy những thay đổi này.

Khi di chuyển dự án sang các nền tảng, bạn phải chuyển đổi các thay đổi như return { "//command_line_option:cpu": "arm" } thành return { "//command_line_option:platforms": "//:my_arm_platform" } hoặc sử dụng các mối liên kết nền tảng để hỗ trợ cả hai kiểu thông qua cửa sổ di chuyển.

Cách sử dụng các nền tảng hiện nay

Nếu chỉ muốn tạo hoặc biên dịch chéo một dự án, bạn nên làm theo tài liệu chính thức của dự án. Người duy trì ngôn ngữ và dự án có trách nhiệm xác định cách thức và thời điểm tích hợp với các nền tảng, cũng như giá trị mà việc tích hợp mang lại.

Nếu là người duy trì dự án, ngôn ngữ hoặc chuỗi công cụ và bản dựng của bạn không sử dụng các nền tảng theo mặc định, thì bạn có 3 lựa chọn (ngoài việc chờ quá trình di chuyển trên toàn cầu):

  1. Bật cờ "sử dụng nền tảng" cho các ngôn ngữ của dự án (nếu có) và thực hiện mọi hoạt động kiểm thử cần thiết để xem các dự án mà bạn quan tâm có hoạt động hay không.

  2. Nếu các dự án mà bạn quan tâm vẫn phụ thuộc vào các cờ cũ như --cpu--crosstool_top, hãy sử dụng các cờ này cùng với --platforms:

    bazel build //:my_mixed_project --platforms==//:myplatform --cpu=... --crosstool_top=...

    Việc này có một số chi phí bảo trì (bạn phải đảm bảo chế độ cài đặt khớp với nhau theo cách thủ công). Nhưng điều này sẽ hoạt động khi không có quá trình chuyển đổi không hợp lệ.

  3. Viết các ánh xạ nền tảng để hỗ trợ cả hai kiểu bằng cách ánh xạ các chế độ cài đặt theo kiểu --cpu sang các nền tảng tương ứng và ngược lại.

Liên kết nền tảng

Ánh xạ nền tảng là một API tạm thời cho phép logic dựa trên nền tảng và logic dựa trên phiên bản cũ cùng tồn tại trong cùng một bản dựng thông qua cửa sổ không dùng nữa của phiên bản cũ.

Hoạt động liên kết nền tảng là hoạt động liên kết platform() với một tập hợp cờ cũ tương ứng hoặc ngược lại. Ví dụ:

platforms:
  # Maps "--platforms=//platforms:ios" to "--cpu=ios_x86_64 --apple_platform_type=ios".
  //platforms:ios
    --cpu=ios_x86_64
    --apple_platform_type=ios

flags:
  # Maps "--cpu=ios_x86_64 --apple_platform_type=ios" to "--platforms=//platforms:ios".
  --cpu=ios_x86_64
  --apple_platform_type=ios
    //platforms:ios

  # Maps "--cpu=darwin --apple_platform_type=macos" to "//platform:macos".
  --cpu=darwin
  --apple_platform_type=macos
    //platforms:macos

Bazel sử dụng chế độ này để đảm bảo tất cả các chế độ cài đặt (cả dựa trên nền tảng và chế độ cũ) đều được áp dụng nhất quán trong suốt quá trình tạo, kể cả thông qua các quá trình chuyển đổi.

Theo mặc định, Bazel đọc các mối liên kết từ tệp platform_mappings trong thư mục gốc của không gian làm việc. Bạn cũng có thể đặt --platform_mappings=//:my_custom_mapping.

Xem tại đây để biết toàn bộ thông tin chi tiết.

Câu hỏi

Để được hỗ trợ chung và giải đáp thắc mắc về tiến trình di chuyển, hãy liên hệ với bazel-discuss@googlegroups.com hoặc chủ sở hữu của các quy tắc thích hợp.

Để thảo luận về thiết kế và sự phát triển của các API nền tảng/chuỗi công cụ, hãy liên hệ với bazel-dev@googlegroups.com.

Xem thêm