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

Bazel có hỗ trợ tinh vi cho việc lập mô hình 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 cẩn thận giữa các chủ sở hữu mã, nhà duy trì quy tắc và nhà phát triển chính của Bazel.

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

tl;dr: Các API chuỗi công cụ và nền tảng của Bazel đã 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ệp tham chiếu cũ khác được cập nhật. Công việc này đang diễn ra. Cuối cùng, tất cả bản dựng sẽ dựa trên nền tảng. Hãy đọc thông tin bên dưới để xem bản dựng của bạn phù hợp với những gì.

Để xem tài liệu chính thức hơn, vui lòng xem:

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 nhiều loại máy và xây dựng bằng các công cụ ngôn ngữ phù hợp.

Đây là tính năng mới được bổ sung gần đây cho Bazel. Điều đó truyền cảm hứng bằng cách quan sát các nhà bảo trì ngôn ngữ đã làm việc này theo những 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 chuỗi công cụ C++ và CPU mục tiêu của bản dựng. Không có mạng nào trong số này mô hình hoá đúng một "nền tảng". Những cố gắng làm như vậy trong quá khứ đã gây ra các bản dựng vụng về và không chính xác. Những cờ này cũng không kiểm soát quá trình biên dịch Java. Quá trình này đã phát triển giao diện độc lập riêng với --java_toolchain.

Bazel dành cho các dự án lớn, đa ngôn ngữ và đa nền tảng. Điều này đòi hỏi việc hỗ trợ nguyên tắc hơn cho các khái niệm này, bao gồm cả những API rõ ràng khuyến khích ngôn ngữ và khả năng tương tác của 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 các dự án thực sự sử dụng chúng. Điều này không hề đơn giản vì logic quy tắc, chuỗi công cụ, phần phụ thuộc và select() của dự án phải hỗ trợ các phần tử đó. Điều này đòi hỏi một trình tự di chuyển cẩn thận để tất cả dự án và các phần phụ thuộc của dự án đó hoạt động đúng cách.

Ví dụ: Quy tắc C++ của Bazel hỗ trợ các nền tảng. 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 người khác thì có thể. Vì vậy, việc cho phép tất cả các bản dựng C++ trên toàn cầu vẫn chưa an toàn.

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 hoàn tất khi tất cả dự án đượ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ể dự đoán 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 chuỗi công cụ chính xác từ //:myplatform.
  3. Một trong hai dự án tuỳ thuộc vào hỗ trợ //:myplatform hoặc dự án của bạn hỗ trợ các API cũ (như --crosstool_top).
  4. //:myplatform tham chiếu đến [các khai báo phổ biến][Khai báo chung về nền tảng]{: .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 nhiều dự án.
  5. select() của tất cả dự án có liên quan đều hiểu được các thuộc tính máy mà //:myplatform ngụ ý.
  6. //:myplatform được xác định ở một vị trí 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 là dành riêng cho dự án của bạn, nếu không thì sẽ ở đâu đó tất 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 để dự án một số nền tảng và chuỗi công cụ.

Tôi có nên sử dụng các nền tảng 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à nhà bảo trì dự án, ngôn ngữ hoặc chuỗi công cụ, thì cuối cùng bạn sẽ cần hỗ trợ các API mới. Việc bạn đợi cho đến khi quá trình di chuyển toàn cầu hoàn tất hay chọn sử dụng sớm tuỳ thuộc vào nhu cầu cụ thể của bạn về giá trị / chi phí:

Giá trị

  • Bạn có thể select() hoặc chọn chuỗi công cụ trên đú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.
  • Nhiều bản dựng chính xác hơn. Nếu bạn select() bằng --cpu trong ví dụ trên, thì hãy thêm một CPU mới có hỗ trợ cùng một tập lệnh, thì select() sẽ không nhận ra 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. Mọi dự án đều hiểu: --platforms=//:myplatform. Không cần nhiều cờ cho nhiều ngôn ngữ trên dòng lệnh.
  • Thiết kế ngôn ngữ đơn giản hơn. Mọi ngôn ngữ đều có 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 nền tảng.
  • Các mục tiêu có thể bị bỏ qua trong giai đoạn xây dựng và thử nghiệm nếu các mục tiêu đó 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.
  • Để những thiết bị này hoạt động, bạn có thể phải bảo trì tạm thời thêm.
  • Để API mới và API cũ cùng tồn tại, bạn cần hướng dẫn người dùng cẩn thận hơn để tránh nhầm lẫn.
  • Các định nghĩa chuẩn cho các thuộc tính phổ biến như OSCPU vẫn đang phát triển và có thể yêu cầu thêm nội dung đóng góp ban đầu.
  • Các định nghĩa chính tắc cho các chuỗi công cụ dành riêng cho từng ngôn ngữ vẫn đang phát triển và có thể cần thêm một số lượt đóng góp ban đầu.

Xem xét API

platform là một tập hợp các 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 máy. Các giá trị có cùng "loại" được nhóm lại 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 thuộc tính này khai báo các công cụ của ngôn ngữ (chẳng hạn như compiler = "//mytoolchain:custom_gcc"). Trình cung cấp của nó sẽ chuyển thông tin này đến các quy tắc cần tạo bằng các 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 mục tiêu (target_compatible_with = ["@platforms//os:linux"]) và những máy mà các công cụ đó 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 bản dựng và tạo tệp nhị phân cho //:myplatform. Đây gọi là độ phân giải của chuỗi công cụ.

Bạn có thể đăng ký tập hợp 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ợ hiện tại của nền tảng còn tuỳ theo ngôn ngữ. Tất cả các quy tắc chính của Bazel đều chuyển sang nền tảng. Nhưng quá trình này sẽ mất thời gian. Điều này là vì 3 lý do chính:

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

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

    Các định nghĩa về nền tảng cũng cần thiết (trừ phi bạn xây dựng cho cùng một máy mà Bazel chạy). 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ó. Bạn cũng phải di chuyển select()lượt chuyển đổi. Đâ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ả các ngôn ngữ không thể đọ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 làm cho 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, theo đó giá trị sẽ tăng lên khi API nền tảng trở nên phổ biến hơn.

Các thuộc tính phổ biến của nền tảng

Bạn phải khai báo các thuộc tính nền tảng như OSCPU phổ biến trong các dự án ở một nơi tiêu chuẩn và tập trung. Điều này khuyến khích nhiều dự án và khả năng tương thích giữa nhiều ngôn ngữ.

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

Các thuộc tính phổ biến 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 trong ví dụ trên là @platforms//cpu:arm). Các thuộc tính ngôn ngữ phổ biến phải được khai báo trong kho lưu trữ của 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 nền tảng rõ ràng để mô tả các loại máy mà họ muốn xây dựng trên đó. Sau đó, các trình xử lý này được kích hoạt bằng --platforms.

Khi không đặt --platforms, Bazel sẽ mặc định sử dụng platform đại diện cho máy tạo bản dựng cục bộ. Chuỗi này được tạo tự động tại @local_config_platform//:host, do đó, bạn không cần phải xác định rõ ràng. Công cụ này liên kết OSCPU của máy cục bộ với các constraint_value được khai báo trong @platforms.

C++

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

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

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

thay vì chế độ 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à các selectlượt chuyển đổi của bạn tương thích. Hãy xem phần #7260Định cấu hình chuỗi công cụ C++ để được hướng dẫn thêm.

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 các 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 các quy tắc của Apple về việc di chuyển sang nền tả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 sử dụng 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 các nền tảng để chọn chuỗi công cụ khi bạn thiết lập --incompatible_enable_android_toolchain_resolution.

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

Quả táo

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

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

Ngôn ngữ khác

Nếu bạn đang thiết kế 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ụ cho ngôn ngữ của bạn. Hãy xem tài liệu về chuỗi công cụ để biết hướng dẫn từng bước đầy đủ.

select()

Các dự án có thể select() trên constraint_value mục tiêu nhưng không thể hoàn thành các nền tảng. Việc này là có chủ ý để các select() hỗ trợ nhiều loại máy nhất có thể. Thư viện có các nguồn dành riêng cho ARM phải hỗ trợ tất cả máy sử dụng ARM trừ khi 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 theo cách thông thường trên --cpu:

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

Tìm hiểu thêm 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 các dự án đó sang constraint_values hoặc sử dụng 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

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 quá trình chuyển đổi đặ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" } sang return { "//command_line_option:platforms": "//:my_arm_platform" } hoặc sử dụng tính năng 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 đó. Các nhà bảo trì ngôn ngữ và dự án sẽ quyết định 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ị mang lại.

Nếu bạn là nhà bảo 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 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 toàn cục):

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

  2. Nếu các dự án 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 sẽ tốn một chút chi phí bảo trì (bạn phải đảm bảo các chế độ cài đặt khớp với nhau theo cách thủ công). Tuy nhiên, điều này sẽ hiệu quả trong trường hợp từ chối chuyển đổi.

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

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

Liên kết 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 cũ cùng tồn tại trong cùng một bản dựng thông qua cửa sổ ngừng sử dụng sau này.

Ánh xạ nền tảng là bản đồ của platform() với một tập hợp cờ cũ tương ứng hoặc 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 tính năng này để đảm bảo tất cả các chế độ cài đặt, cả dựa trên nền tảng và cũ, đều được áp dụng một cách nhất quán trong suốt quá trình tạo bản dựng, bao gồm cả quá trình chuyển đổi.

Theo mặc định, Bazel đọ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.

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

Câu hỏi

Nếu cần được hỗ trợ chung và có câu hỏi 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