完全指南:構建、交叉編譯與不兼容目標跳過的底層機制)
Bazel 平臺與約束Platforms Constraints完全指南構建、交叉編譯與不兼容目標跳過的底層機制【免費下載鏈接】bazela fast, scalable, multi-language and extensible build system項目地址: https://gitcode.com/GitHub_Trending/ba/bazel導讀Bazel 需要在多種硬件、操作系統與系統配置上構建和測試代碼這通常涉及不同版本的鏈接器、編譯器乃至遠程執行集群。為管理這一復雜性Bazel 提供了約束constraints與平臺platforms兩個核心概念用約束描述機器的可區分屬性用平臺描述一臺完整的機器。本文將圍繞docs/concepts/platforms.mdx的完整體系深入講解三種平臺角色Host / Execution / Target、--platforms的指定方式、constraint_setting/constraint_value/platform規則的寫法以及基于target_compatible_with跳過不兼容目標的完整機制并結合本倉庫源碼如 IncompatiblePlatformProvider.java揭示其底層實現。讀完本文你將能夠在自己的 BUILD 文件中獨立定義平臺、為規則聲明平臺兼容性并利用bazel cquery排查不兼容目標。為什么需要約束與平臺Bazel 可以構建面向多種機器的代碼x86 與 Arm 的 CPU 架構、Linux/macOS/Windows 等操作系統、有無 GPU、本地編譯器版本各不相同。這些差異直接決定應該使用哪套編譯工具鏈、哪個src文件以及哪些依賴。約束constraint構建機器或生產機器的一種可區分屬性distinguishing property。常見約束包括 CPU 架構、GPU 有無、本地安裝的編譯器版本。但約束可以是任何在編排構建任務時有意義地區分機器的屬性——完全由你定義。平臺platform一組約束的集合用于描述一臺完整的機器。Bazel 用平臺概念讓開發者決定為哪些機器構建目標平臺、由哪些機器執行編譯與測試動作執行平臺、構建動作應使用哪套工具鏈。此外約束還能配合 select() 在構建規則上做自定義屬性與依賴選擇例如當構建目標是 Arm 機器時使用src_arm.cc。這與可配置屬性configurable attributes機制是一體的。平臺的三種角色Bazel 識別一個平臺可能扮演的三種角色角色含義Host宿主Bazel 自身運行所在的平臺Execution執行運行編譯動作以產出構建結果的平臺Target目標被構建的代碼最終要運行于其上的平臺需要注意一次構建只有一個 Host 平臺但往往有多個 Execution 與 Target 平臺。例如遠程 Linux CI 機器與開發者本地的 Mac 可能同時執行構建動作移動 App 的代碼則面向多種手機型號與硬件擴展。構建與平臺的三種關系一次構建與平臺之間通常存在三種關系形態單平臺構建Single-platformHost、Execution、Target 三者相同。典型場景是在開發者機器上不使用遠程執行地直接構建并在同一臺機器上運行產物。交叉編譯構建Cross-compilationHost 與 Execution 相同但 Target 不同。例如在 MacBook Pro 上不啟用遠程執行構建 iOS App。多平臺構建Multi-platformHost、Execution、Target 三者互不相同。例如在 MacBook Pro 上開發同時用遠程 Linux 機器編譯不需要 Xcode 的 C 動作——Host 是 macOSExecution 是 Linux CI 機器Target 是 iOS 設備。從源碼結構看這三者的區分在配置層由PlatformOptions管理--host_platform與--target_platform分別控制宿主與目標平臺且--host_platform的舊名稱是experimental_host_platform見 PlatformOptions.java。執行平臺則由遠程執行/動態執行策略按動作選擇。指定平臺--platforms標志開發者使用平臺最常見的方式是通過--platforms標志指定期望的目標機器$ bazel build //:my_linux_app --platforms//myplatforms:linux_x86由于各組織的構建機器配置差異很大一般由組織自行維護平臺定義BUILD 文件中的platform規則。當未設置--platforms時其默認值是platforms//host。該平臺是特殊定義的會自動探測 Bazel 運行所在機器的 OS 與 CPU 屬性從而使構建默認面向 Bazel 所在的那臺機器。構建規則可以借助platforms//os與platforms//cpu中的約束配合select()基于這些屬性做分支選擇。Bazel 內部將默認宿主平臺別名指向bazel_tools//tools:host_platform這一點在源碼中有直接對應PlatformOptions.java中定義了DEFAULT_HOST_PLATFORM bazel_tools//tools:host_platform并通過--host_platform標志oldName experimental_host_platform暴露給用戶。常用的約束與平臺為保持生態一致性Bazel 團隊維護了一個包含主流 CPU 架構與操作系統約束定義的倉庫platforms。其中預定義了platforms//os下的各操作系統約束值platforms//cpu下的各 CPU 架構約束值platforms//hostBazel 內置的特殊平臺定義別名bazel_tools//tools:host_platform自動探測 Bazel 運行機器的 OS 與 CPU 屬性。在你的 MODULE 中platforms通常由 Bazel 自動提供作為內置模塊可直接在 BUILD 文件中以platforms//os:linux、platforms//cpu:x86_64等標簽引用。定義約束constraint_setting 與 constraint_value約束通過constraint_setting與constraint_value兩個構建規則建模。constraint_setting聲明一種屬性類型constraint_setting(name cpu)constraint_value聲明該屬性的一個可能取值constraint_value( name x86, constraint_setting :cpu )上述示例若定義在cpus/BUILD中即可用標簽//cpus:x86在定義平臺或定制構建規則時引用。如果可見性允許你也可以為已有的constraint_setting擴展自定義取值——例如為platforms//cpu補充一個尚不存在的架構值。在集成測試中可以找到更完整的用法示例target_compatible_with_test.sh 的set_up()中同時定義了foo_version、bar_version兩個constraint_setting以及各自的多個constraint_value并以不同組合構造出多個平臺。定義平臺platform 規則platform構建規則把平臺定義為一組constraint_value的集合platform( name linux_x86, constraint_values [ platforms//os:linux, platforms//cpu:x86, ], )這描述了一臺必須同時滿足platforms//os:linux與platforms//cpu:x86的機器。平臺對同一個constraint_setting只能有一個constraint_value。這意味著一個平臺不能同時聲明兩個 CPU除非你新建另一種constraint_setting類型來建模第二個取值。底層實現會在沖突時直接報錯Platform.java中調用platformBuilder.build()時若出現重復約束會拋出ConstraintCollection.DuplicateConstraintException并定位到constraint_values屬性見 Platform.java。從Platform.java的實現可以進一步了解platform規則的完整能力面源碼parents繼承父平臺且只允許單個父平臺多于一個會觸發屬性錯誤constraint_values顯式聲明的約束值集合exec_properties附加到該平臺執行動作上的遠程執行屬性鍵值對flags平臺附加標志列表required_settings平臺生效所需的config_setting匹配條件check_toolchain_types/allowed_toolchain_types可選地校驗該平臺允許使用的工具鏈類型missing_toolchain_error缺少工具鏈時的自定義報錯文案。注意Platform.java的注釋還指出一個約束若平臺設置了host_platform或target_platform屬性為 true會自動納入探測到的 CPU 與 OS 約束此時再在constraint_values中重復添加同類約束會報錯。跳過不兼容目標target_compatible_with當針對特定目標平臺構建時往往希望跳過在該平臺上永遠不會工作的目標。例如在 Linux 機器上用//...全量構建時Windows 設備驅動很可能產生大量編譯錯誤。此時使用target_compatible_with通用屬性告知 Bazel 你的代碼需要滿足哪些目標平臺約束。最簡單的用法是把目標限制到單一平臺目標不會為任何無法滿足全部約束的平臺構建。以下示例把win_driver_lib.cc限制為僅 64 位 Windows 可用cc_library( name win_driver_lib, srcs [win_driver_lib.cc], target_compatible_with [ platforms//cpu:x86_64, platforms//os:windows, ], ):win_driver_lib僅與 64 位 Windows 構建兼容與其他所有平臺都不兼容。不兼容性是傳遞的transitive任何傳遞依賴某個不兼容目標的目標自身也會被判定為不兼容。這一點與IncompatiblePlatformProvider的實現一致——provider 中專門記錄了因哪些不兼容依賴而導致自身不兼容targetsResponsibleForIncompatibility字段。目標在什么時機被跳過當不兼容目標作為**目標模式展開target pattern expansion**的一部分被納入構建時它會被跳過。下面兩種調用都會跳過模式展開中發現的不兼容目標$ bazel build --platforms//:myplatform //...$ bazel build --platforms//:myplatform //:alltest_suite中不兼容的測試若該test_suite通過--expand_test_suites在命令行展開同樣會被跳過。換句話說命令行上的test_suite目標表現得像:all和...。使用--noexpand_test_suites可阻止展開此時包含不兼容測試的test_suite目標自身也會變成不兼容。注test_suite定義見 general 參考文檔。如果在命令行顯式指定一個不兼容目標構建會直接報錯并失敗$ 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若啟用--skip_incompatible_explicit_targets則顯式指定的不兼容目標會被靜默跳過而不是報錯。更富表達力的約束platforms還提供了platforms//:incompatible這一特殊的constraint_value任何平臺都不會滿足它。把 select() 與platforms//:incompatible結合可以表達更復雜的限制例如實現基礎的 OR 邏輯。下面這個例子把庫標記為僅兼容 macOS 與 Linuxcc_library( name unixish_lib, srcs [unixish_lib.cc], target_compatible_with select({ platforms//os:osx: [], platforms//os:linux: [], //conditions:default: [platforms//:incompatible], }), )其語義可解讀為目標為 macOS 時該目標無約束目標為 Linux 時該目標無約束其他情況下目標帶有platforms//:incompatible約束由于該約束不屬于任何平臺目標被判定為不兼容。注意空的約束列表等價于與一切兼容。反向排除式兼容也可以類似表達。下面的例子描述一個除了 ARM 之外與所有平臺兼容的庫cc_library( name non_arm_lib, srcs [non_arm_lib.cc], target_compatible_with select({ platforms//cpu:arm: [platforms//:incompatible], //conditions:default: [], }), )為提升約束表達的可讀性可使用 skylibbazel-skylib倉庫提供的selects.with_or()輔助函數來聲明更直觀的或邏輯。倉庫集成測試target_compatible_with_test.sh中也有利用selects.config_setting_group(match_any ...)組合config_setting的先例可參考其寫法。值得一提的實現細節target_compatible_with中不僅可以放約束值也可以直接放config_setting標簽。IncompatiblePlatformProvider會區分三種不兼容成因并分別記錄約束未滿足constraintsResponsibleForIncompatibility記錄目標平臺未滿足的約束列表按標簽字符串排序、去重config_setting 未匹配configSettingsResponsibleForIncompatibility記錄未匹配的config_setting標簽依賴不兼容targetsResponsibleForIncompatibility記錄導致不兼容的傳遞依賴。具體字段定義見 IncompatiblePlatformProvider.java其構造邏輯保證排序與去重在該 provider 內一次性完成方便下游直接消費。用 bazel cquery 檢測不兼容目標可以在bazel cquery的 Starlark 輸出格式中使用IncompatiblePlatformProvider來區分不兼容與兼容目標。利用它可以過濾掉不兼容目標。下面的例子只打印兼容目標的標簽不兼容目標不打印$ cat example.cquery def format(target): if IncompatiblePlatformProvider not in providers(target): return target.label return $ bazel cquery //... --outputstarlark --starlark:fileexample.cquery在 Starlark 中IncompatiblePlatformProvider這個名字來自IncompatiblePlatformProvider.java中定義的STARLARK_NAME IncompatiblePlatformProvider源碼因此providers(target)的鍵與之一一對應。已知問題不兼容目標會忽略可見性限制即不受 visibility 約束對應 bazelbuild/bazel 的 issue #16044。這意味著在排查某目標為何仍被納入構建時不能依賴可見性配置來兜底而應顯式使用target_compatible_with聲明兼容性。結合源碼的完整工作流小結在 MODULE 中確保platforms可用優先復用其//os、//cpu下的標準約束用constraint_settingconstraint_value定義組織特有的屬性如 GPU 型號、編譯器版本參考 target_compatible_with_test.sh 中foo_version/bar_version的寫法用platform規則把這些約束組合成具體機器可用parents繼承宿主平臺以減少重復參考Platform.java支持的全部屬性在命令行用--platforms必要時配合--host_platform見 PlatformOptions.java選擇目標機器為平臺相關代碼聲明target_compatible_with配合platforms//:incompatible與select()表達精確的兼容集合用bazel cquery --outputstarlark結合IncompatiblePlatformProvider驗證哪些目標被判定為不兼容。這套機制同時是工具鏈解析toolchains與遠程執行Execution 平臺選擇的基石平臺決定在哪構建、為誰構建工具鏈決定用什么編譯二者共同支撐起 Bazel 在異構環境下的可擴展構建能力。延伸閱讀工具鏈Toolchains平臺如何參與工具鏈解析可配置屬性Configurable Attributes基于約束的select()用法platforms-and-toolchains 構建規則參考constraint_setting/constraint_value/platform完整參數IncompatiblePlatformProvider 參考cquery 檢測所用的 providercquery 指南Starlark 輸出格式與查詢技巧【免費下載鏈接】bazela fast, scalable, multi-language and extensible build system項目地址: https://gitcode.com/GitHub_Trending/ba/bazel創作聲明:本文部分內容由AI輔助生成(AIGC),僅供參考