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

Báo cáo vấn đề Xem nguồn

Bazel có khả năng hỗ trợ tinh vi cho việc lập mô hình nền tảngchuỗi công cụ. Để tích hợp tính năng này với các dự án thực tế, bạn phải hợp tác cẩn thận giữa các chủ sở hữu mã, bên duy trì quy tắc và các 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à cho biết cách xây dựng bằng các nền tảng đó.

tl;dr: 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ệ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ả các bản dựng đều 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 ở đâu.

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

Thông tin khái quát

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

Đây là tính năng bổ sung tương đối mới cho Bazel. Điều này được truyền cảm hứng khi quan sát thấy rằng các nhà duy trì ngôn ngữ đã thực hiện 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ó tính năng nào trong số này mô hình chính xác một "nền tảng". Những nỗ lực làm như vậy trước đây đã tạo ra các bản dựng khó sử dụng 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, vốn đã phát triển giao diện độc lập của riêng nó bằng --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 sự hỗ trợ theo 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 củ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 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ợ chúng. Để đảm bảo tất cả dự án và phần phụ thuộc của các dự án đó hoạt động chính xác, bạn phải thực hiện một trình tự di chuyển cẩn thận.

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 một số khác thì có thể. Vì vậy, việc bật toàn bộ các nền tảng cho mọi bản dựng C++ 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 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 định dạng:

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à 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 dự án (tuỳ thuộc vào dự án của bạn) có 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 [thông tin khai báo phổ biến][Khai báo nền tảng chung]{: .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 và 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, còn không thì ở một nơi nào đó, tất cả dự án có thể sử dụng nền tảng này có thể tìm thấy ở một nơi nào đó.

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 mà các dự án chọn nền tảng và chuỗi công cụ.

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

Nếu chỉ muốn xây dựng 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ì bạn nên hỗ trợ các API mới. Việc bạn đợi cho đến khi quá trình di chuyển chung hoàn tất hay chọn tham gia sớm 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 đúng các thuộc tính mà bạn quan tâm thay vì cờ được cố định giá trị trong mã 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 thể 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. Tất cả dự án đều hiểu rằng: --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ả 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.
  • Các mục tiêu có thể bị bỏ qua 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.
  • Để chúng hoạt động, bạn có thể cần phải bảo trì tạm thời thêm.
  • Để các API mới và API cũ cùng tồn tại, bạn cần phải hướng dẫn người dùng kỹ lưỡng 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 bạn phải đóng góp thêm ban đầu.
  • Các định nghĩa chuẩn cho các chuỗi công cụ theo ngôn ngữ cụ thể vẫn đang phát triển và có thể yêu cầu đóng góp thêm ban đầu.

Đánh giá API

platform là 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ị thuộc cùng một "loại" được nhóm vào 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 lớp này khai báo các công cụ của ngôn ngữ (như compiler = "//mytoolchain:custom_gcc"). Nhà cung cấp của lớp này truyền thông tin này đến các quy tắc cần xây dựng bằng các công cụ này.

Chuỗi công cụ khai báo constraint_value máy mà chúng có thể nhắm mục tiêu (target_compatible_with = ["@platforms//os:linux"]) và các máy mà 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 và tạo tệp nhị phân cho //:myplatform. Đây gọi là quá trình giải quyết bằng 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.

Xem tại đây để biết thông tin chi tiết hơn.

Trạng thái

Khả năng hỗ trợ nền tảng hiện tại sẽ khác nhau giữa các ngôn ngữ. Mọi quy tắc chính của Bazel đều được chuyển sang các nền tảng. Tuy nhiên, quá trình này sẽ mất nhiều thời gian. Điều này là do ba lý do chính:

  1. Bạn phải cập nhật logic của quy tắc để nhận thông tin về công cụ từ API chuỗi công cụ mới (ctx.toolchains) 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. Nhà duy trì chuỗi công cụ phải xác định chuỗi công cụ và giúp người dùng có thể truy cập vào chuỗi công cụ đó (trong kho lưu trữ GitHub và các mục nhập WORKSPACE). Đây là quy trình đơn giản về mặt kỹ thuật 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.

    Bạn cũng cần định nghĩa nền tảng (trừ phi bạn tạo bản dựng 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ó. Các select()chuyển đổi cũng phải được di chuyển. Đây là thách thức lớn nhất. Việc 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 mọi ngôn ngữ đều 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, với giá trị ngày càng tăng 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

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

Ví dụ: nếu MyAppselect() trên constraint_value @myapp//cpus:armSomeCommonLib có một select() trên @commonlib//constraints:arm, thì các chế độ 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 phổ biến trên toàn cầu được khai báo trong kho lưu trữ @platforms (vì vậy, nhãn chuẩn cho ví dụ trên là @platforms//cpu:arm). Bạn phải khai báo các thuộc tính phổ biến của ngôn ngữ 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 các nền tảng rõ ràng để mô tả các loại máy mà họ muốn xây dựng. Sau đó, các biến này được kích hoạt bằng --platforms.

Khi bạn không thiết lập --platforms, Bazel sẽ mặc định dùng một platform đại diện cho máy xây dựng cục bộ. Mã này được tạo tự động tại @local_config_platform//:host nên bạn không cần phải xác định rõ ràng. Thao tá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ì 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 bị phụ thuộc bởi 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à selectcác hiệu ứng chuyển đổi tương thích. Xem nội dung #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. Lý do là 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 khi 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 đặt --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 thuận lợi.

Quả táo

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

Các phiên bản này cũng không hỗ trợ các phần phụ thuộc C++ dựa trên nền tảng vì các phần phụ thuộc này sử dụng --crosstool_top cũ để đặt chuỗi công cụ C++. Cho đến khi di chuyển này, bạn có thể kết hợp các dự án Apple với C++ có hỗ trợ platorm với cá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ụ của ngôn ngữ của bạn. Vui lòng xem tài liệu về chuỗi công cụ để nắm được hướng dẫn từng bước hữu ích.

select()

Các dự án có thể select() trên constraint_value mục tiêu nhưng không thể hoàn tất các nền tảng. Mục đích của việc này là để 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ả các máy hỗ trợ 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 truyền thống trên --cpu:

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

Bạn có thể 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 các dự án đó thành constraint_values 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.

Kiểu chuyển cảnh

Hiệu ứng chuyển đổi Starlark sẽ thay đổi cờ ở các phần trên biểu đồ bản dựng. Nếu dự án của bạn sử dụng hiệu ứng chuyển đổi đặt --cpu, --crossstool_top hoặ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 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 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 xây dựng 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 đó. Những đơn vị duy trì ngôn ngữ và dự án sẽ quyết đị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 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 nền tảng theo mặc định, thì bạn có 3 lựa chọn (ngoài việc chờ di chuyển chung):

  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 thử nghiệm cần thiết để 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ũ 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 tốn một ít chi phí bảo trì (bạn phải tự đảm bảo chế độ cài đặt khớp với nhau). Tuy nhiên, phương thức này sẽ hoạt động trong trường hợp không xảy ra các hiệu ứng chuyển đổi nổi loạn.

  3. Viết mối liên kết 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 đến 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 nền tảng 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 của nền tảng cũ.

Liên kết nền tảng là bản đồ ánh xạ platform() với một bộ cờ cũ tương ứng hoặc cờ ngược. 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 phương pháp 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 nhất quán trong suốt quá trình xây dựng, kể cả khi 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ể thiết lập --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à 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à quá trình phát triển của các API chuỗi công cụ/nền tảng, hãy liên hệ với bazel-dev@googlegroups.com.

Xem thêm